Repository: cassandra Updated Branches: refs/heads/trunk e58a40e52 -> 9072757b5
Automatically grant permissions to creators of new objects & roles patch by Sam Tunnicliffe; reviewed by Aleksey Yeschenko for CASSANDRA-7216 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/9072757b Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/9072757b Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/9072757b Branch: refs/heads/trunk Commit: 9072757b52d9a6c443871c5fba443c27606b8e57 Parents: e58a40e Author: Sam Tunnicliffe <[email protected]> Authored: Fri Feb 6 10:32:21 2015 +0000 Committer: Aleksey Yeschenko <[email protected]> Committed: Sat Feb 14 01:57:17 2015 +0300 ---------------------------------------------------------------------- CHANGES.txt | 2 +- NEWS.txt | 3 ++ .../cassandra/auth/AllowAllAuthorizer.java | 13 +++----- .../cassandra/auth/AuthenticatedUser.java | 15 +++++++-- .../org/apache/cassandra/auth/IAuthorizer.java | 15 +++++++++ .../statements/CreateKeyspaceStatement.java | 28 ++++++++++------ .../cql3/statements/CreateRoleStatement.java | 33 ++++++++++++++++++- .../cql3/statements/CreateTableStatement.java | 34 ++++++++++++++------ .../statements/ListPermissionsStatement.java | 9 +++++- .../statements/SchemaAlteringStatement.java | 34 +++++++++++++++++++- 10 files changed, 152 insertions(+), 34 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index e1ae69b..70166c1 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ 3.0 - * Add role based access control (CASSANDRA-7653, 8650) + * Add role based access control (CASSANDRA-7653, 8650, 7216) * 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/9072757b/NEWS.txt ---------------------------------------------------------------------- diff --git a/NEWS.txt b/NEWS.txt index 801cea7..fc5b514 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -30,6 +30,9 @@ New features subject of permissions. Users (roles) can now be granted permissions on other roles, including CREATE, ALTER, DROP & AUTHORIZE, which removesthe need for superuser privileges in order to perform user/role management operations. + - Creators of database resources (Keyspaces, Tables, Roles) are now automatically + granted all permissions on them (if the IAuthorizer implementation supports + this). - SSTable file name is changed. Now you don't have Keyspace/CF name in file name. Also, secondary index has its own directory under parent's directory. http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java b/src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java index 7a60a14..bc6fee4 100644 --- a/src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/AllowAllAuthorizer.java @@ -20,25 +20,21 @@ package org.apache.cassandra.auth; import java.util.Collections; import java.util.Set; -import org.apache.cassandra.exceptions.InvalidRequestException; - public class AllowAllAuthorizer implements IAuthorizer { public Set<Permission> authorize(AuthenticatedUser user, IResource resource) { - return Permission.ALL; + return resource.applicablePermissions(); } public void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, RoleResource to) - throws InvalidRequestException { - throw new InvalidRequestException("GRANT operation is not supported by AllowAllAuthorizer"); + throw new UnsupportedOperationException("GRANT operation is not supported by AllowAllAuthorizer"); } public void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, RoleResource from) - throws InvalidRequestException { - throw new InvalidRequestException("REVOKE operation is not supported by AllowAllAuthorizer"); + throw new UnsupportedOperationException("REVOKE operation is not supported by AllowAllAuthorizer"); } public void revokeAllFrom(RoleResource droppedRole) @@ -50,9 +46,8 @@ public class AllowAllAuthorizer implements IAuthorizer } public Set<PermissionDetails> list(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, RoleResource of) - throws InvalidRequestException { - throw new InvalidRequestException("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer"); + throw new UnsupportedOperationException("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer"); } public Set<IResource> protectedResources() http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/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 25d2ed4..e4a065d 100644 --- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java +++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java @@ -27,8 +27,6 @@ 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.ScheduledExecutors; import org.apache.cassandra.config.DatabaseDescriptor; @@ -43,7 +41,8 @@ import org.apache.cassandra.exceptions.RequestValidationException; */ public class AuthenticatedUser { - private static final Logger logger = LoggerFactory.getLogger(AuthenticatedUser.class); + public static final String SYSTEM_USERNAME = "system"; + public static final AuthenticatedUser SYSTEM_USER = new AuthenticatedUser(SYSTEM_USERNAME); public static final String ANONYMOUS_USERNAME = "anonymous"; public static final AuthenticatedUser ANONYMOUS_USER = new AuthenticatedUser(ANONYMOUS_USERNAME); @@ -106,6 +105,16 @@ public class AuthenticatedUser } /** + * Some internal operations are performed on behalf of Cassandra itself, in those cases + * the system user should be used where an identity is required + * see CreateRoleStatement#execute() and overrides of SchemaAlteringStatement#grantPermissionsToCreator() + */ + public boolean isSystem() + { + return this == SYSTEM_USER; + } + + /** * 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 http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/src/java/org/apache/cassandra/auth/IAuthorizer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/IAuthorizer.java b/src/java/org/apache/cassandra/auth/IAuthorizer.java index c3e98e2..21b64dc 100644 --- a/src/java/org/apache/cassandra/auth/IAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/IAuthorizer.java @@ -45,6 +45,8 @@ public interface IAuthorizer /** * Grants a set of permissions on a resource to a role. * The opposite of revoke(). + * This method is optional and may be called internally, so implementations which do + * not support it should be sure to throw UnsupportedOperationException. * * @param performer User who grants the permissions. * @param permissions Set of permissions to grant. @@ -53,6 +55,7 @@ public interface IAuthorizer * * @throws RequestValidationException * @throws RequestExecutionException + * @throws java.lang.UnsupportedOperationException */ void grant(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, RoleResource grantee) throws RequestValidationException, RequestExecutionException; @@ -60,6 +63,8 @@ public interface IAuthorizer /** * Revokes a set of permissions on a resource from a user. * The opposite of grant(). + * This method is optional and may be called internally, so implementations which do + * not support it should be sure to throw UnsupportedOperationException. * * @param performer User who revokes the permissions. * @param permissions Set of permissions to revoke. @@ -68,12 +73,15 @@ public interface IAuthorizer * * @throws RequestValidationException * @throws RequestExecutionException + * @throws java.lang.UnsupportedOperationException */ void revoke(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, RoleResource revokee) throws RequestValidationException, RequestExecutionException; /** * Returns a list of permissions on a resource granted to a role. + * This method is optional and may be called internally, so implementations which do + * not support it should be sure to throw UnsupportedOperationException. * * @param performer User who wants to see the permissions. * @param permissions Set of Permission values the user is interested in. The result should only include the @@ -87,6 +95,7 @@ public interface IAuthorizer * * @throws RequestValidationException * @throws RequestExecutionException + * @throws java.lang.UnsupportedOperationException */ Set<PermissionDetails> list(AuthenticatedUser performer, Set<Permission> permissions, IResource resource, RoleResource grantee) throws RequestValidationException, RequestExecutionException; @@ -95,16 +104,22 @@ public interface IAuthorizer * Called before deleting a role with DROP ROLE statement (or the alias provided for compatibility, * DROP USER) so that a new role with the same name wouldn't inherit permissions of the deleted one in the future. * This removes all permissions granted to the Role in question. + * This method is optional and may be called internally, so implementations which do + * not support it should be sure to throw UnsupportedOperationException. * * @param revokee The role to revoke all permissions from. + * @throws java.lang.UnsupportedOperationException */ void revokeAllFrom(RoleResource revokee); /** * This method is called after a resource is removed (i.e. keyspace, table or role is dropped) and revokes all * permissions granted on the IResource in question. + * This method is optional and may be called internally, so implementations which do + * not support it should be sure to throw UnsupportedOperationException. * * @param droppedResource The resource to revoke all permissions on. + * @throws java.lang.UnsupportedOperationException */ void revokeAllOn(IResource droppedResource); http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java index 8281cbd..2dc9c44 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateKeyspaceStatement.java @@ -17,18 +17,12 @@ */ package org.apache.cassandra.cql3.statements; -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.auth.*; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; -import org.apache.cassandra.exceptions.AlreadyExistsException; -import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.exceptions.RequestValidationException; -import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.exceptions.*; import org.apache.cassandra.locator.AbstractReplicationStrategy; -import org.apache.cassandra.service.ClientState; -import org.apache.cassandra.service.MigrationManager; -import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.service.*; import org.apache.cassandra.thrift.ThriftValidation; import org.apache.cassandra.transport.Event; @@ -116,4 +110,20 @@ public class CreateKeyspaceStatement extends SchemaAlteringStatement { return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, keyspace()); } + + protected void grantPermissionsToCreator(QueryState state) + { + try + { + DataResource resource = DataResource.keyspace(keyspace()); + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + resource.applicablePermissions(), + resource, + RoleResource.role(state.getClientState().getUser().getName())); + } + catch (RequestExecutionException e) + { + throw new RuntimeException(e); + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java index 65d588b..347d20a 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java @@ -17,8 +17,10 @@ */ package org.apache.cassandra.cql3.statements; -import org.apache.cassandra.auth.*; +import org.apache.cassandra.auth.AuthenticatedUser; import org.apache.cassandra.auth.IRoleManager.Option; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.auth.RoleResource; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.RoleName; import org.apache.cassandra.cql3.RoleOptions; @@ -76,6 +78,35 @@ public class CreateRoleStatement extends AuthenticationStatement return null; DatabaseDescriptor.getRoleManager().createRole(state.getUser(), role, opts.getOptions()); + grantPermissionsToCreator(state); return null; } + + /** + * Grant all applicable permissions on the newly created role to the user performing the request + * see also: SchemaAlteringStatement#grantPermissionsToCreator and the overridden implementations + * of it in subclasses CreateKeyspaceStatement & CreateTableStatement. + * @param state + */ + private void grantPermissionsToCreator(ClientState state) + { + // The creator of a Role automatically gets ALTER/DROP/AUTHORIZE permissions on it if: + // * the user is not anonymous + // * the configured IAuthorizer supports granting of permissions (not all do, AllowAllAuthorizer doesn't and + // custom external implementations may not) + if (!state.getUser().isAnonymous()) + { + try + { + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + role.applicablePermissions(), + role, + RoleResource.role(state.getUser().getName())); + } + catch (UnsupportedOperationException e) + { + // not a problem, grant is an optional method on IAuthorizer + } + } + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java index 4ec656b..3be20a6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateTableStatement.java @@ -20,23 +20,23 @@ package org.apache.cassandra.cql3.statements; import java.nio.ByteBuffer; import java.util.*; -import org.apache.cassandra.exceptions.*; -import org.apache.commons.lang3.StringUtils; import com.google.common.collect.HashMultiset; import com.google.common.collect.Multiset; +import org.apache.commons.lang3.StringUtils; -import org.apache.cassandra.auth.Permission; -import org.apache.cassandra.config.ColumnDefinition; -import org.apache.cassandra.config.CFMetaData; -import org.apache.cassandra.config.Schema; -import org.apache.cassandra.cql3.*; -import org.apache.cassandra.db.composites.*; +import org.apache.cassandra.auth.*; +import org.apache.cassandra.config.*; +import org.apache.cassandra.cql3.CFName; +import org.apache.cassandra.cql3.CQL3Type; +import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.db.ColumnFamilyType; +import org.apache.cassandra.db.composites.*; import org.apache.cassandra.db.marshal.*; -import org.apache.cassandra.exceptions.AlreadyExistsException; +import org.apache.cassandra.exceptions.*; import org.apache.cassandra.io.compress.CompressionParameters; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Event; import org.apache.cassandra.utils.ByteBufferUtil; @@ -119,6 +119,22 @@ public class CreateTableStatement extends SchemaAlteringStatement return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily()); } + protected void grantPermissionsToCreator(QueryState state) + { + try + { + IResource resource = DataResource.table(keyspace(), columnFamily()); + DatabaseDescriptor.getAuthorizer().grant(AuthenticatedUser.SYSTEM_USER, + resource.applicablePermissions(), + resource, + RoleResource.role(state.getClientState().getUser().getName())); + } + catch (RequestExecutionException e) + { + throw new RuntimeException(e); + } + } + /** * Returns a CFMetaData instance based on the parameters parsed from this * <code>CREATE</code> statement, or defaults where applicable. http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java b/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java index 375d160..58f8e9c 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ListPermissionsStatement.java @@ -102,7 +102,14 @@ public class ListPermissionsStatement extends AuthorizationStatement private Set<PermissionDetails> list(ClientState state, IResource resource) throws RequestValidationException, RequestExecutionException { - return DatabaseDescriptor.getAuthorizer().list(state.getUser(), permissions, resource, grantee); + try + { + return DatabaseDescriptor.getAuthorizer().list(state.getUser(), permissions, resource, grantee); + } + catch (UnsupportedOperationException e) + { + throw new InvalidRequestException(e.getMessage()); + } } private ResultMessage resultMessage(List<PermissionDetails> details) http://git-wip-us.apache.org/repos/asf/cassandra/blob/9072757b/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java b/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java index b0d67ac..a477df6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SchemaAlteringStatement.java @@ -17,10 +17,12 @@ */ package org.apache.cassandra.cql3.statements; +import org.apache.cassandra.auth.AuthenticatedUser; import org.apache.cassandra.cql3.CFName; import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.cql3.QueryOptions; -import org.apache.cassandra.exceptions.*; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Event; @@ -66,6 +68,17 @@ public abstract class SchemaAlteringStatement extends CFStatement implements CQL public abstract Event.SchemaChange changeEvent(); /** + * Schema alteration may result in a new database object (keyspace, table, role, function) being created capable of + * having permissions GRANTed on it. The creator of the object (the primary role assigned to the AuthenticatedUser + * performing the operation) is automatically granted ALL applicable permissions on the object. This is a hook for + * subclasses to override in order to perform that grant when the statement is executed. + */ + protected void grantPermissionsToCreator(QueryState state) + { + // no-op by default + } + + /** * Announces the migration to other nodes in the cluster. * @return true if the execution of this statement resulted in a schema change, false otherwise (when IF NOT EXISTS * is used, for example) @@ -82,6 +95,25 @@ public abstract class SchemaAlteringStatement extends CFStatement implements CQL return new ResultMessage.Void(); Event.SchemaChange ce = changeEvent(); + + // when a schema alteration results in a new db object being created, we grant permissions on the new + // object to the user performing the request if: + // * the user is not anonymous + // * the configured IAuthorizer supports granting of permissions (not all do, AllowAllAuthorizer doesn't and + // custom external implementations may not) + AuthenticatedUser user = state.getClientState().getUser(); + if (user != null && !user.isAnonymous() && ce != null && ce.change == Event.SchemaChange.Change.CREATED) + { + try + { + grantPermissionsToCreator(state); + } + catch (UnsupportedOperationException e) + { + // not a problem, grant is an optional method on IAuthorizer + } + } + return ce == null ? new ResultMessage.Void() : new ResultMessage.SchemaChange(ce); }
