Repository: cassandra Updated Branches: refs/heads/trunk 63945228f -> 54de771e6
Add network authz Patch by Blake Eggleston; Reviewed by Sam Tunnicliffe for CASSANDRA-13985 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/54de771e Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/54de771e Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/54de771e Branch: refs/heads/trunk Commit: 54de771e643e9cc64d1f5dd28b5de8a9a91a219e Parents: 6394522 Author: Blake Eggleston <bdeggles...@gmail.com> Authored: Wed Dec 13 13:17:05 2017 -0800 Committer: Blake Eggleston <bdeggles...@gmail.com> Committed: Mon Apr 23 09:55:31 2018 -0700 ---------------------------------------------------------------------- CHANGES.txt | 1 + NEWS.txt | 4 + conf/cassandra.yaml | 10 + doc/source/cql/security.rst | 19 ++ pylib/cqlshlib/cql3handling.py | 2 + src/antlr/Lexer.g | 2 + src/antlr/Parser.g | 26 +- .../auth/AllowAllNetworkAuthorizer.java | 47 ++++ .../org/apache/cassandra/auth/AuthConfig.java | 10 +- .../org/apache/cassandra/auth/AuthKeyspace.java | 10 +- .../cassandra/auth/AuthenticatedUser.java | 7 + .../cassandra/auth/CassandraAuthorizer.java | 26 +- .../auth/CassandraNetworkAuthorizer.java | 157 +++++++++++ .../cassandra/auth/CassandraRoleManager.java | 18 +- .../apache/cassandra/auth/DCPermissions.java | 217 ++++++++++++++++ .../cassandra/auth/INetworkAuthorizer.java | 63 +++++ .../apache/cassandra/auth/NetworkAuthCache.java | 41 +++ .../org/apache/cassandra/config/Config.java | 1 + .../cassandra/config/DatabaseDescriptor.java | 12 + .../cql3/statements/AlterRoleStatement.java | 16 +- .../cql3/statements/CreateRoleStatement.java | 13 +- .../cql3/statements/DropRoleStatement.java | 1 + .../cql3/statements/ListRolesStatement.java | 5 +- .../cql3/statements/ListUsersStatement.java | 5 +- .../org/apache/cassandra/dht/Datacenters.java | 63 +++++ .../locator/NetworkTopologyStrategy.java | 26 +- .../apache/cassandra/service/ClientState.java | 6 + .../cassandra/service/StorageService.java | 1 + .../org/apache/cassandra/utils/FBUtilities.java | 15 ++ .../unit/org/apache/cassandra/SchemaLoader.java | 20 ++ .../auth/CassandraNetworkAuthorizerTest.java | 259 +++++++++++++++++++ .../config/DatabaseDescriptorRefTest.java | 1 + .../cql3/statements/AlterRoleStatementTest.java | 73 ++++++ .../statements/CreateRoleStatementTest.java | 72 ++++++ .../statements/CreateUserStatementTest.java | 46 ++++ .../cql3/validation/operations/CreateTest.java | 5 +- 36 files changed, 1246 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 4cdd8ba..6976c7f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.0 + * Add network authz (CASSANDRA-13985) * Use the correct IP/Port for Streaming when localAddress is left unbound (CASSANDAR-14389) * nodetool listsnapshots is missing local system keyspace snapshots (CASSANDRA-14381) * Remove StreamCoordinator.streamExecutor thread pool (CASSANDRA-14402) http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/NEWS.txt ---------------------------------------------------------------------- diff --git a/NEWS.txt b/NEWS.txt index 9216bc0..a13f633 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -72,6 +72,10 @@ New features See CASSANDRA-13848 for more detail - Metric for coordinator writes per table has been added. See CASSANDRA-14232 - Nodetool cfstats now has options to sort by various metrics as well as limit results. + - Operators can restrict login user activity to one or more datacenters. See `network_authorizer` + in cassandra.yaml, and the docs for create and alter role statements. CASSANDRA-13985 + - Roles altered from login=true to login=false will prevent existing connections from executing any + statements after the cache has been refreshed. CASSANDRA-13985 Upgrading --------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/conf/cassandra.yaml ---------------------------------------------------------------------- diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index d466072..7e4b2c2 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -122,6 +122,16 @@ authorizer: AllowAllAuthorizer # increase system_auth keyspace replication factor if you use this role manager. role_manager: CassandraRoleManager +# Network authorization backend, implementing INetworkAuthorizer; used to restrict user +# access to certain DCs +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllNetworkAuthorizer, +# CassandraNetworkAuthorizer}. +# +# - AllowAllNetworkAuthorizer allows access to any DC to any user - set it to disable authorization. +# - CassandraNetworkAuthorizer stores permissions in system_auth.network_permissions table. Please +# increase system_auth keyspace replication factor if you use this authorizer. +network_authorizer: AllowAllNetworkAuthorizer + # Validity period for roles cache (fetching granted roles can be an expensive # operation depending on the role manager, CassandraRoleManager is one example) # Granted roles are cached for authenticated sessions in AuthenticatedUser and http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/doc/source/cql/security.rst ---------------------------------------------------------------------- diff --git a/doc/source/cql/security.rst b/doc/source/cql/security.rst index ada6517..53b194e 100644 --- a/doc/source/cql/security.rst +++ b/doc/source/cql/security.rst @@ -46,6 +46,8 @@ Creating a role uses the ``CREATE ROLE`` statement: :| LOGIN '=' `boolean` :| SUPERUSER '=' `boolean` :| OPTIONS '=' `map_literal` + :| ACCESS TO DATACENTERS `set_literal` + :| ACCESS TO ALL DATACENTERS For instance:: @@ -53,6 +55,8 @@ For instance:: CREATE ROLE alice WITH PASSWORD = 'password_a' AND LOGIN = true; CREATE ROLE bob WITH PASSWORD = 'password_b' AND LOGIN = true AND SUPERUSER = true; CREATE ROLE carlos WITH OPTIONS = { 'custom_option1' : 'option1_value', 'custom_option2' : 99 }; + CREATE ROLE alice WITH PASSWORD = 'password_a' AND LOGIN = true AND ACCESS TO DATACENTERS {'DC1', 'DC3'}; + CREATE ROLE alice WITH PASSWORD = 'password_a' AND LOGIN = true AND ACCESS TO ALL DATACENTERS; By default roles do not possess ``LOGIN`` privileges or ``SUPERUSER`` status. @@ -81,6 +85,14 @@ quotation marks. If internal authentication has not been set up or the role does not have ``LOGIN`` privileges, the ``WITH PASSWORD`` clause is not necessary. +Restricting connections to specific datacenters +``````````````````````````````````````````````` + +If a ``network_authorizer`` has been configured, you can restrict login roles to specific datacenters with the +``ACCESS TO DATACENTERS`` clause followed by a set literal of datacenters the user can access. Not specifiying +datacenters implicitly grants access to all datacenters. The clause ``ACCESS TO ALL DATACENTERS`` can be used for +explicitness, but there's no functional difference. + Creating a role conditionally ````````````````````````````` @@ -105,6 +117,13 @@ For instance:: ALTER ROLE bob WITH PASSWORD = 'PASSWORD_B' AND SUPERUSER = false; +Restricting connections to specific datacenters +``````````````````````````````````````````````` + +If a ``network_authorizer`` has been configured, you can restrict login roles to specific datacenters with the +``ACCESS TO DATACENTERS`` clause followed by a set literal of datacenters the user can access. To remove any +data center restrictions, use the ``ACCESS TO ALL DATACENTERS`` clause. + Conditions on executing ``ALTER ROLE`` statements: - A client must have ``SUPERUSER`` status to alter the ``SUPERUSER`` status of another role http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/pylib/cqlshlib/cql3handling.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index 314c431..5d7a3c0 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -1468,6 +1468,8 @@ syntax_rules += r''' | "OPTIONS" "=" <mapLiteral> | "SUPERUSER" "=" <boolean> | "LOGIN" "=" <boolean> + | "ACCESS" "TO" "DATACENTERS" <setLiteral> + | "ACCESS" "TO" "ALL" "DATACENTERS" ; <dropRoleStatement> ::= "DROP" "ROLE" <rolename> http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/antlr/Lexer.g ---------------------------------------------------------------------- diff --git a/src/antlr/Lexer.g b/src/antlr/Lexer.g index 23b556e..b9d4c1e 100644 --- a/src/antlr/Lexer.g +++ b/src/antlr/Lexer.g @@ -145,6 +145,8 @@ K_PASSWORD: P A S S W O R D; K_LOGIN: L O G I N; K_NOLOGIN: N O L O G I N; K_OPTIONS: O P T I O N S; +K_ACCESS: A C C E S S; +K_DATACENTERS: D A T A C E N T E R S; K_CLUSTERING: C L U S T E R I N G; K_ASCII: A S C I I; http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/antlr/Parser.g ---------------------------------------------------------------------- diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g index d6ea210..f32fd48 100644 --- a/src/antlr/Parser.g +++ b/src/antlr/Parser.g @@ -1160,7 +1160,7 @@ createUserStatement returns [CreateRoleStatement stmt] ( K_WITH userPassword[opts] )? ( K_SUPERUSER { superuser = true; } | K_NOSUPERUSER { superuser = false; } )? { opts.setOption(IRoleManager.Option.SUPERUSER, superuser); - $stmt = new CreateRoleStatement(name, opts, ifNotExists); } + $stmt = new CreateRoleStatement(name, opts, DCPermissions.all(), ifNotExists); } ; /** @@ -1175,7 +1175,7 @@ alterUserStatement returns [AlterRoleStatement stmt] ( K_WITH userPassword[opts] )? ( K_SUPERUSER { opts.setOption(IRoleManager.Option.SUPERUSER, true); } | K_NOSUPERUSER { opts.setOption(IRoleManager.Option.SUPERUSER, false); } ) ? - { $stmt = new AlterRoleStatement(name, opts); } + { $stmt = new AlterRoleStatement(name, opts, null); } ; /** @@ -1208,10 +1208,11 @@ listUsersStatement returns [ListRolesStatement stmt] createRoleStatement returns [CreateRoleStatement stmt] @init { RoleOptions opts = new RoleOptions(); + DCPermissions.Builder dcperms = DCPermissions.builder(); boolean ifNotExists = false; } : K_CREATE K_ROLE (K_IF K_NOT K_EXISTS { ifNotExists = true; })? name=userOrRoleName - ( K_WITH roleOptions[opts] )? + ( K_WITH roleOptions[opts, dcperms] )? { // set defaults if they weren't explictly supplied if (!opts.getLogin().isPresent()) @@ -1222,7 +1223,7 @@ createRoleStatement returns [CreateRoleStatement stmt] { opts.setOption(IRoleManager.Option.SUPERUSER, false); } - $stmt = new CreateRoleStatement(name, opts, ifNotExists); + $stmt = new CreateRoleStatement(name, opts, dcperms.build(), ifNotExists); } ; @@ -1238,10 +1239,11 @@ createRoleStatement returns [CreateRoleStatement stmt] alterRoleStatement returns [AlterRoleStatement stmt] @init { RoleOptions opts = new RoleOptions(); + DCPermissions.Builder dcperms = DCPermissions.builder(); } : K_ALTER K_ROLE name=userOrRoleName - ( K_WITH roleOptions[opts] )? - { $stmt = new AlterRoleStatement(name, opts); } + ( K_WITH roleOptions[opts, dcperms] )? + { $stmt = new AlterRoleStatement(name, opts, dcperms.isModified() ? dcperms.build() : null); } ; /** @@ -1269,15 +1271,21 @@ listRolesStatement returns [ListRolesStatement stmt] { $stmt = new ListRolesStatement(grantee, recursive); } ; -roleOptions[RoleOptions opts] - : roleOption[opts] (K_AND roleOption[opts])* +roleOptions[RoleOptions opts, DCPermissions.Builder dcperms] + : roleOption[opts, dcperms] (K_AND roleOption[opts, dcperms])* ; -roleOption[RoleOptions opts] +roleOption[RoleOptions opts, DCPermissions.Builder dcperms] : K_PASSWORD '=' v=STRING_LITERAL { opts.setOption(IRoleManager.Option.PASSWORD, $v.text); } | K_OPTIONS '=' m=fullMapLiteral { opts.setOption(IRoleManager.Option.OPTIONS, convertPropertyMap(m)); } | K_SUPERUSER '=' b=BOOLEAN { opts.setOption(IRoleManager.Option.SUPERUSER, Boolean.valueOf($b.text)); } | K_LOGIN '=' b=BOOLEAN { opts.setOption(IRoleManager.Option.LOGIN, Boolean.valueOf($b.text)); } + | K_ACCESS K_TO K_ALL K_DATACENTERS { dcperms.all(); } + | K_ACCESS K_TO K_DATACENTERS '{' dcPermission[dcperms] (',' dcPermission[dcperms])* '}' + ; + +dcPermission[DCPermissions.Builder builder] + : dc=STRING_LITERAL { builder.add($dc.text); } ; // for backwards compatibility in CREATE/ALTER USER, this has no '=' http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/AllowAllNetworkAuthorizer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/AllowAllNetworkAuthorizer.java b/src/java/org/apache/cassandra/auth/AllowAllNetworkAuthorizer.java new file mode 100644 index 0000000..17c04d5 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/AllowAllNetworkAuthorizer.java @@ -0,0 +1,47 @@ +/* + * 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 org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.exceptions.InvalidRequestException; + +public class AllowAllNetworkAuthorizer implements INetworkAuthorizer +{ + public void setup() {} + + public DCPermissions authorize(RoleResource role) + { + return DCPermissions.all(); + } + + public void setRoleDatacenters(RoleResource role, DCPermissions permissions) + { + throw new InvalidRequestException("ACCESS TO DATACENTERS operations not supported by AllowAllNetworkAuthorizer"); + } + + public void drop(RoleResource role) {} + + public void validateConfiguration() throws ConfigurationException {} + + @Override + public boolean requireAuthorization() + { + return false; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/AuthConfig.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/AuthConfig.java b/src/java/org/apache/cassandra/auth/AuthConfig.java index 2ca1522..cc38296 100644 --- a/src/java/org/apache/cassandra/auth/AuthConfig.java +++ b/src/java/org/apache/cassandra/auth/AuthConfig.java @@ -25,7 +25,6 @@ import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.utils.FBUtilities; -import org.hsqldb.Database; /** * Only purpose is to Initialize authentication/authorization via {@link #applyAuth()}. @@ -98,12 +97,21 @@ public final class AuthConfig if (conf.internode_authenticator != null) DatabaseDescriptor.setInternodeAuthenticator(FBUtilities.construct(conf.internode_authenticator, "internode_authenticator")); + // network authorizer + INetworkAuthorizer networkAuthorizer = FBUtilities.newNetworkAuthorizer(conf.network_authorizer); + DatabaseDescriptor.setNetworkAuthorizer(networkAuthorizer); + if (networkAuthorizer.requireAuthorization() && !authenticator.requireAuthentication()) + { + throw new ConfigurationException(conf.network_authorizer + " can't be used with " + conf.authenticator, false); + } + // Validate at last to have authenticator, authorizer, role-manager and internode-auth setup // in case these rely on each other. authenticator.validateConfiguration(); authorizer.validateConfiguration(); roleManager.validateConfiguration(); + networkAuthorizer.validateConfiguration(); DatabaseDescriptor.getInternodeAuthenticator().validateConfiguration(); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/AuthKeyspace.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/AuthKeyspace.java b/src/java/org/apache/cassandra/auth/AuthKeyspace.java index 9a9dffe..1f71bdc 100644 --- a/src/java/org/apache/cassandra/auth/AuthKeyspace.java +++ b/src/java/org/apache/cassandra/auth/AuthKeyspace.java @@ -39,6 +39,7 @@ public final class AuthKeyspace public static final String ROLE_MEMBERS = "role_members"; public static final String ROLE_PERMISSIONS = "role_permissions"; public static final String RESOURCE_ROLE_INDEX = "resource_role_permissons_index"; + public static final String NETWORK_PERMISSIONS = "network_permissions"; public static final long SUPERUSER_SETUP_DELAY = Long.getLong("cassandra.superuser_setup_delay_ms", 10000); @@ -78,6 +79,13 @@ public final class AuthKeyspace + "role text," + "PRIMARY KEY(resource, role))"); + private static final TableMetadata NetworkPermissions = + parse(NETWORK_PERMISSIONS, + "user network permissions", + "CREATE TABLE %s (" + + "role text, " + + "dcs frozen<set<text>>, " + + "PRIMARY KEY(role))"); private static TableMetadata parse(String name, String description, String cql) { @@ -92,6 +100,6 @@ public final class AuthKeyspace { return KeyspaceMetadata.create(SchemaConstants.AUTH_KEYSPACE_NAME, KeyspaceParams.simple(1), - Tables.of(Roles, RoleMembers, RolePermissions, ResourceRoleIndex)); + Tables.of(Roles, RoleMembers, RolePermissions, ResourceRoleIndex, NetworkPermissions)); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/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 5e57308..c608068 100644 --- a/src/java/org/apache/cassandra/auth/AuthenticatedUser.java +++ b/src/java/org/apache/cassandra/auth/AuthenticatedUser.java @@ -22,6 +22,7 @@ import java.util.Set; import com.google.common.base.Objects; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.dht.Datacenters; /** * Returned from IAuthenticator#authenticate(), represents an authenticated user everywhere internally. @@ -39,6 +40,7 @@ public class AuthenticatedUser // User-level permissions cache. private static final PermissionsCache permissionsCache = new PermissionsCache(DatabaseDescriptor.getAuthorizer()); + private static final NetworkAuthCache networkAuthCache = new NetworkAuthCache(DatabaseDescriptor.getNetworkAuthorizer()); private final String name; // primary Role of the logged in user @@ -104,6 +106,11 @@ public class AuthenticatedUser return permissionsCache.getPermissions(this, resource); } + public boolean hasLocalAccess() + { + return networkAuthCache.get(this.getPrimaryRole()).canAccess(Datacenters.thisDatacenter()); + } + @Override public String toString() { http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java index d760dce..b6b2e54 100644 --- a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java @@ -57,7 +57,7 @@ public class CassandraAuthorizer implements IAuthorizer private static final String RESOURCE = "resource"; private static final String PERMISSIONS = "permissions"; - private SelectStatement authorizeRoleStatement; + SelectStatement authorizeRoleStatement; public CassandraAuthorizer() { @@ -180,11 +180,7 @@ public class CassandraAuthorizer implements IAuthorizer BatchStatement.Type.LOGGED, Lists.newArrayList(Iterables.filter(statements, ModificationStatement.class)), Attributes.none()); - QueryProcessor.instance.processBatch(batch, - QueryState.forInternalCalls(), - BatchQueryOptions.withoutPerStatementVariables(QueryOptions.DEFAULT), - System.nanoTime()); - + processBatch(batch); } // Add every permission on the resource granted to the role @@ -195,7 +191,8 @@ public class CassandraAuthorizer implements IAuthorizer Lists.newArrayList(ByteBufferUtil.bytes(role.getRoleName()), ByteBufferUtil.bytes(resource.getName()))); - ResultMessage.Rows rows = authorizeRoleStatement.execute(QueryState.forInternalCalls(), options, System.nanoTime()); + ResultMessage.Rows rows = select(authorizeRoleStatement, options); + UntypedResultSet result = UntypedResultSet.create(rows.result); if (!result.isEmpty() && result.one().has(PERMISSIONS)) @@ -346,8 +343,21 @@ public class CassandraAuthorizer implements IAuthorizer return StringUtils.replace(name, "'", "''"); } - private UntypedResultSet process(String query) throws RequestExecutionException + ResultMessage.Rows select(SelectStatement statement, QueryOptions options) + { + return statement.execute(QueryState.forInternalCalls(), options, System.nanoTime()); + } + + UntypedResultSet process(String query) throws RequestExecutionException { return QueryProcessor.process(query, ConsistencyLevel.LOCAL_ONE); } + + void processBatch(BatchStatement statement) + { + QueryProcessor.instance.processBatch(statement, + QueryState.forInternalCalls(), + BatchQueryOptions.withoutPerStatementVariables(QueryOptions.DEFAULT), + System.nanoTime()); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/CassandraNetworkAuthorizer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/CassandraNetworkAuthorizer.java b/src/java/org/apache/cassandra/auth/CassandraNetworkAuthorizer.java new file mode 100644 index 0000000..9faa423 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/CassandraNetworkAuthorizer.java @@ -0,0 +1,157 @@ +/* + * 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 com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; + +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.cql3.statements.SelectStatement; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.schema.SchemaConstants; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.messages.ResultMessage; +import org.apache.cassandra.utils.ByteBufferUtil; + +public class CassandraNetworkAuthorizer implements INetworkAuthorizer +{ + private SelectStatement authorizeUserStatement = null; + + public void setup() + { + String query = String.format("SELECT dcs FROM %s.%s WHERE role = ?", + SchemaConstants.AUTH_KEYSPACE_NAME, + AuthKeyspace.NETWORK_PERMISSIONS); + authorizeUserStatement = (SelectStatement) QueryProcessor.getStatement(query, ClientState.forInternalCalls()).statement; + } + + @VisibleForTesting + ResultMessage.Rows select(SelectStatement statement, QueryOptions options) + { + return statement.execute(QueryState.forInternalCalls(), options, System.nanoTime()); + } + + @VisibleForTesting + void process(String query) + { + QueryProcessor.process(query, ConsistencyLevel.LOCAL_ONE); + } + + private Set<String> getAuthorizedDcs(String name) + { + QueryOptions options = QueryOptions.forInternalCalls(ConsistencyLevel.LOCAL_ONE, + Lists.newArrayList(ByteBufferUtil.bytes(name))); + + ResultMessage.Rows rows = select(authorizeUserStatement, options); + UntypedResultSet result = UntypedResultSet.create(rows.result); + Set<String> dcs = null; + if (!result.isEmpty() && result.one().has("dcs")) + { + dcs = result.one().getFrozenSet("dcs", UTF8Type.instance); + } + return dcs; + } + + public DCPermissions authorize(RoleResource role) + { + if (!DatabaseDescriptor.getRoleManager().canLogin(role)) + { + return DCPermissions.none(); + } + if (Roles.hasSuperuserStatus(role)) + { + return DCPermissions.all(); + } + + Set<String> dcs = getAuthorizedDcs(role.getName()); + + if (dcs == null || dcs.isEmpty()) + { + return DCPermissions.all(); + } + else + { + return DCPermissions.subset(dcs); + } + } + + private static String getSetString(DCPermissions permissions) + { + if (permissions.restrictsAccess()) + { + StringBuilder builder = new StringBuilder(); + builder.append('{'); + boolean first = true; + for (String dc: permissions.allowedDCs()) + { + if (first) + { + first = false; + } + else + { + builder.append(", "); + } + builder.append('\''); + builder.append(dc); + builder.append('\''); + } + builder.append('}'); + return builder.toString(); + } + else + { + return "{}"; + } + } + + public void setRoleDatacenters(RoleResource role, DCPermissions permissions) + { + String query = String.format("UPDATE %s.%s SET dcs = %s WHERE role = '%s'", + SchemaConstants.AUTH_KEYSPACE_NAME, + AuthKeyspace.NETWORK_PERMISSIONS, + getSetString(permissions), + role.getName()); + + process(query); + } + + public void drop(RoleResource role) + { + String query = String.format("DELETE FROM %s.%s WHERE role = '%s'", + SchemaConstants.AUTH_KEYSPACE_NAME, + AuthKeyspace.NETWORK_PERMISSIONS, + role.getName()); + + process(query); + } + + public void validateConfiguration() throws ConfigurationException + { + // noop + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/CassandraRoleManager.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index 7333310..1271699 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -21,6 +21,7 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.*; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; @@ -388,6 +389,12 @@ public class CassandraRoleManager implements IRoleManager } } + @VisibleForTesting + ResultMessage.Rows select(SelectStatement statement, QueryOptions options) + { + return statement.execute(QueryState.forInternalCalls(), options, System.nanoTime()); + } + /* * Get a single Role instance given the role name. This never returns null, instead it * uses the null object NULL_ROLE when a role with the given name cannot be found. So @@ -395,11 +402,9 @@ public class CassandraRoleManager implements IRoleManager */ private Role getRole(String name) { - ResultMessage.Rows rows = - loadRoleStatement.execute(QueryState.forInternalCalls(), - QueryOptions.forInternalCalls(consistencyForRole(name), - Collections.singletonList(ByteBufferUtil.bytes(name))), - System.nanoTime()); + QueryOptions options = QueryOptions.forInternalCalls(consistencyForRole(name), + Collections.singletonList(ByteBufferUtil.bytes(name))); + ResultMessage.Rows rows = select(loadRoleStatement, options); if (rows.result.isEmpty()) return NULL_ROLE; @@ -498,7 +503,8 @@ public class CassandraRoleManager implements IRoleManager * This shouldn't be used during setup as this will directly return an error if the manager is not setup yet. Setup tasks * should use QueryProcessor.process directly. */ - private UntypedResultSet process(String query, ConsistencyLevel consistencyLevel) throws RequestValidationException, RequestExecutionException + @VisibleForTesting + UntypedResultSet process(String query, ConsistencyLevel consistencyLevel) throws RequestValidationException, RequestExecutionException { if (!isClusterReady) throw new InvalidRequestException("Cannot process role related query as the role manager isn't yet setup. " http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/DCPermissions.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/DCPermissions.java b/src/java/org/apache/cassandra/auth/DCPermissions.java new file mode 100644 index 0000000..46cdad9 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/DCPermissions.java @@ -0,0 +1,217 @@ +/* + * 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.HashSet; +import java.util.Set; +import java.util.StringJoiner; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +import org.apache.cassandra.dht.Datacenters; +import org.apache.cassandra.exceptions.InvalidRequestException; + +public abstract class DCPermissions +{ + public abstract boolean canAccess(String dc); + public abstract boolean restrictsAccess(); + public abstract Set<String> allowedDCs(); + public abstract void validate(); + + private static class SubsetPermissions extends DCPermissions + { + private final Set<String> subset; + + public SubsetPermissions(Set<String> subset) + { + Preconditions.checkNotNull(subset); + this.subset = subset; + } + + public boolean canAccess(String dc) + { + return subset.contains(dc); + } + + public boolean restrictsAccess() + { + return true; + } + + public Set<String> allowedDCs() + { + return ImmutableSet.copyOf(subset); + } + + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SubsetPermissions that = (SubsetPermissions) o; + + return subset.equals(that.subset); + } + + public int hashCode() + { + return subset.hashCode(); + } + + public String toString() + { + StringJoiner joiner = new StringJoiner(", "); + subset.forEach(joiner::add); + return joiner.toString(); + } + + public void validate() + { + Datacenters.getValidDatacenters(); + + Set<String> unknownDcs = Sets.difference(subset, Datacenters.getValidDatacenters()); + if (!unknownDcs.isEmpty()) + { + throw new InvalidRequestException(String.format("Invalid value(s) for DATACENTERS '%s'," + + "All values must be valid datacenters", subset)); + } + } + } + + private static final DCPermissions ALL = new DCPermissions() + { + public boolean canAccess(String dc) + { + return true; + } + + public boolean restrictsAccess() + { + return false; + } + + public Set<String> allowedDCs() + { + throw new UnsupportedOperationException(); + } + + public String toString() + { + return "ALL"; + } + + public void validate() + { + + } + }; + + private static final DCPermissions NONE = new DCPermissions() + { + public boolean canAccess(String dc) + { + return false; + } + + public boolean restrictsAccess() + { + return true; + } + + public Set<String> allowedDCs() + { + throw new UnsupportedOperationException(); + } + + public String toString() + { + return "n/a"; + } + + public void validate() + { + throw new UnsupportedOperationException(); + } + }; + + public static DCPermissions all() + { + return ALL; + } + + public static DCPermissions none() + { + return NONE; + } + + public static DCPermissions subset(Set<String> dcs) + { + return new SubsetPermissions(dcs); + } + + public static DCPermissions subset(String... dcs) + { + return subset(Sets.newHashSet(dcs)); + } + + public static class Builder + { + private Set<String> dcs = new HashSet<>(); + private boolean isAll = false; + private boolean modified = false; + + public void add(String dc) + { + Preconditions.checkArgument(!isAll, "All has been set"); + dcs.add(dc); + modified = true; + } + + public void all() + { + Preconditions.checkArgument(dcs.isEmpty(), "DCs have already been set"); + isAll = true; + modified = true; + } + + public boolean isModified() + { + return modified; + } + + public DCPermissions build() + { + if (dcs.isEmpty()) + { + return DCPermissions.all(); + } + else + { + return subset(dcs); + } + } + } + + public static Builder builder() + { + return new Builder(); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/INetworkAuthorizer.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/INetworkAuthorizer.java b/src/java/org/apache/cassandra/auth/INetworkAuthorizer.java new file mode 100644 index 0000000..8ff058e --- /dev/null +++ b/src/java/org/apache/cassandra/auth/INetworkAuthorizer.java @@ -0,0 +1,63 @@ +/* + * 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 org.apache.cassandra.exceptions.ConfigurationException; + +/** + * Not part of the roles hierarchy?? How would that even work? + */ +public interface INetworkAuthorizer +{ + /** + * Whether or not the authorizer will attempt authorization. + * If false the authorizer will not be called for authorization of resources. + */ + default boolean requireAuthorization() + { + return true; + } + + /** + * Setup is called once upon system startup to initialize the INetworkAuthorizer. + * + * For example, use this method to create any required keyspaces/column families. + */ + void setup(); + + /** + * Returns the dc permissions associated with the given role + */ + DCPermissions authorize(RoleResource role); + + void setRoleDatacenters(RoleResource role, DCPermissions permissions); + + /** + * Called when a role is deleted, so any corresponding network auth + * data can also be cleaned up + */ + void drop(RoleResource role); + + /** + * Validates configuration of IAuthorizer implementation (if configurable). + * + * @throws ConfigurationException when there is a configuration error. + */ + void validateConfiguration() throws ConfigurationException; +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/auth/NetworkAuthCache.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/auth/NetworkAuthCache.java b/src/java/org/apache/cassandra/auth/NetworkAuthCache.java new file mode 100644 index 0000000..1c82460 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/NetworkAuthCache.java @@ -0,0 +1,41 @@ +/* + * 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 org.apache.cassandra.config.DatabaseDescriptor; + +/** + * Created by blakeeggleston on 12/14/17. + */ +public class NetworkAuthCache extends AuthCache<RoleResource, DCPermissions> implements AuthCacheMBean +{ + public NetworkAuthCache(INetworkAuthorizer authorizer) + { + super("NetworkAuthCache", + DatabaseDescriptor::setRolesValidity, + DatabaseDescriptor::getRolesValidity, + DatabaseDescriptor::setRolesUpdateInterval, + DatabaseDescriptor::getRolesUpdateInterval, + DatabaseDescriptor::setRolesCacheMaxEntries, + DatabaseDescriptor::getRolesCacheMaxEntries, + authorizer::authorize, + () -> DatabaseDescriptor.getAuthenticator().requireAuthentication()); + } + +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/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 ad91a9b..aa4b028 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -52,6 +52,7 @@ public class Config public String authenticator; public String authorizer; public String role_manager; + public String network_authorizer; public volatile int permissions_validity_in_ms = 2000; public volatile int permissions_cache_max_entries = 1000; public volatile int permissions_update_interval_in_ms = -1; http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/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 bf00d40..c738971 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -41,6 +41,7 @@ import org.apache.cassandra.auth.AuthConfig; import org.apache.cassandra.auth.IAuthenticator; import org.apache.cassandra.auth.IAuthorizer; import org.apache.cassandra.auth.IInternodeAuthenticator; +import org.apache.cassandra.auth.INetworkAuthorizer; import org.apache.cassandra.auth.IRoleManager; import org.apache.cassandra.config.Config.CommitLogSync; import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption; @@ -101,6 +102,7 @@ public class DatabaseDescriptor private static IAuthenticator authenticator; private static IAuthorizer authorizer; + private static INetworkAuthorizer networkAuthorizer; // Don't initialize the role manager until applying config. The options supported by CassandraRoleManager // depend on the configured IAuthenticator, so defer creating it until that's been set. private static IRoleManager roleManager; @@ -1066,6 +1068,16 @@ public class DatabaseDescriptor DatabaseDescriptor.authorizer = authorizer; } + public static INetworkAuthorizer getNetworkAuthorizer() + { + return networkAuthorizer; + } + + public static void setNetworkAuthorizer(INetworkAuthorizer networkAuthorizer) + { + DatabaseDescriptor.networkAuthorizer = networkAuthorizer; + } + public static IRoleManager getRoleManager() { return roleManager; http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java b/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java index b910167..64ffe57 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AlterRoleStatement.java @@ -31,18 +31,30 @@ public class AlterRoleStatement extends AuthenticationStatement { private final RoleResource role; private final RoleOptions opts; + final DCPermissions dcPermissions; public AlterRoleStatement(RoleName name, RoleOptions opts) { + this(name, opts, null); + } + + public AlterRoleStatement(RoleName name, RoleOptions opts, DCPermissions dcPermissions) + { this.role = RoleResource.role(name.getName()); this.opts = opts; + this.dcPermissions = dcPermissions; } public void validate(ClientState state) throws RequestValidationException { opts.validate(); - if (opts.isEmpty()) + if (dcPermissions != null) + { + dcPermissions.validate(); + } + + if (opts.isEmpty() && dcPermissions == null) throw new InvalidRequestException("ALTER [ROLE|USER] can't be empty"); // validate login here before checkAccess to avoid leaking user existence to anonymous users. @@ -87,6 +99,8 @@ public class AlterRoleStatement extends AuthenticationStatement { if (!opts.isEmpty()) DatabaseDescriptor.getRoleManager().alterRole(state.getUser(), role, opts); + if (dcPermissions != null) + DatabaseDescriptor.getNetworkAuthorizer().setRoleDatacenters(role, dcPermissions); return null; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/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 ca11eb4..bd9a5a4 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateRoleStatement.java @@ -30,12 +30,14 @@ public class CreateRoleStatement extends AuthenticationStatement { private final RoleResource role; private final RoleOptions opts; + final DCPermissions dcPermissions; private final boolean ifNotExists; - public CreateRoleStatement(RoleName name, RoleOptions options, boolean ifNotExists) + public CreateRoleStatement(RoleName name, RoleOptions options, DCPermissions dcPermissions, boolean ifNotExists) { this.role = RoleResource.role(name.getName()); this.opts = options; + this.dcPermissions = dcPermissions; this.ifNotExists = ifNotExists; } @@ -53,6 +55,11 @@ public class CreateRoleStatement extends AuthenticationStatement { opts.validate(); + if (dcPermissions != null) + { + dcPermissions.validate(); + } + if (role.getRoleName().isEmpty()) throw new InvalidRequestException("Role name can't be an empty string"); @@ -70,6 +77,10 @@ public class CreateRoleStatement extends AuthenticationStatement return null; DatabaseDescriptor.getRoleManager().createRole(state.getUser(), role, opts); + if (dcPermissions.restrictsAccess()) + { + DatabaseDescriptor.getNetworkAuthorizer().setRoleDatacenters(role, dcPermissions); + } grantPermissionsToCreator(state); return null; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/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 9f52f52..a858233 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DropRoleStatement.java @@ -72,6 +72,7 @@ public class DropRoleStatement extends AuthenticationStatement DatabaseDescriptor.getRoleManager().dropRole(state.getUser(), role); DatabaseDescriptor.getAuthorizer().revokeAllFrom(role); DatabaseDescriptor.getAuthorizer().revokeAllOn(role); + DatabaseDescriptor.getNetworkAuthorizer().drop(role); return null; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java b/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java index dc51eb1..47077e7 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ListRolesStatement.java @@ -48,7 +48,8 @@ public class ListRolesStatement extends AuthorizationStatement ImmutableList.of(new ColumnSpecification(KS, CF, new ColumnIdentifier("role", true), UTF8Type.instance), new ColumnSpecification(KS, CF, new ColumnIdentifier("super", true), BooleanType.instance), new ColumnSpecification(KS, CF, new ColumnIdentifier("login", true), BooleanType.instance), - new ColumnSpecification(KS, CF, new ColumnIdentifier("options", true), optionsType)); + new ColumnSpecification(KS, CF, new ColumnIdentifier("options", true), optionsType), + new ColumnSpecification(KS, CF, new ColumnIdentifier("datacenters", true), UTF8Type.instance)); private final RoleResource grantee; private final boolean recursive; @@ -118,12 +119,14 @@ public class ListRolesStatement extends AuthorizationStatement ResultSet result = new ResultSet(resultMetadata); IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); + INetworkAuthorizer networkAuthorizer = DatabaseDescriptor.getNetworkAuthorizer(); for (RoleResource role : sortedRoles) { result.addColumnValue(UTF8Type.instance.decompose(role.getRoleName())); result.addColumnValue(BooleanType.instance.decompose(roleManager.isSuper(role))); result.addColumnValue(BooleanType.instance.decompose(roleManager.canLogin(role))); result.addColumnValue(optionsType.decompose(roleManager.getCustomOptions(role))); + result.addColumnValue(UTF8Type.instance.decompose(networkAuthorizer.authorize(role).toString())); } return new ResultMessage.Rows(result); } http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/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 23a4d56..be3e587 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ListUsersStatement.java @@ -41,7 +41,8 @@ public class ListUsersStatement extends ListRolesStatement private static final List<ColumnSpecification> metadata = ImmutableList.of(new ColumnSpecification(KS, CF, new ColumnIdentifier("name", true), UTF8Type.instance), - new ColumnSpecification(KS, CF, new ColumnIdentifier("super", true), BooleanType.instance)); + new ColumnSpecification(KS, CF, new ColumnIdentifier("super", true), BooleanType.instance), + new ColumnSpecification(KS, CF, new ColumnIdentifier("datacenters", true), UTF8Type.instance)); @Override protected ResultMessage formatResults(List<RoleResource> sortedRoles) @@ -50,12 +51,14 @@ public class ListUsersStatement extends ListRolesStatement ResultSet result = new ResultSet(resultMetadata); IRoleManager roleManager = DatabaseDescriptor.getRoleManager(); + INetworkAuthorizer networkAuthorizer = DatabaseDescriptor.getNetworkAuthorizer(); for (RoleResource role : sortedRoles) { if (!roleManager.canLogin(role)) continue; result.addColumnValue(UTF8Type.instance.decompose(role.getRoleName())); result.addColumnValue(BooleanType.instance.decompose(Roles.hasSuperuserStatus(role))); + result.addColumnValue(UTF8Type.instance.decompose(networkAuthorizer.authorize(role).toString())); } return new ResultMessage.Rows(result); http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/dht/Datacenters.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/dht/Datacenters.java b/src/java/org/apache/cassandra/dht/Datacenters.java new file mode 100644 index 0000000..26ae2e6 --- /dev/null +++ b/src/java/org/apache/cassandra/dht/Datacenters.java @@ -0,0 +1,63 @@ +/* + * 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.dht; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.locator.IEndpointSnitch; +import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.utils.FBUtilities; + +public class Datacenters +{ + + private static class DCHandle + { + private static final String thisDc = DatabaseDescriptor.getEndpointSnitch().getDatacenter(FBUtilities.getBroadcastAddressAndPort()); + } + + public static String thisDatacenter() + { + return DCHandle.thisDc; + } + + /* + * (non-javadoc) Method to generate list of valid data center names to be used to validate the replication parameters during CREATE / ALTER keyspace operations. + * All peers of current node are fetched from {@link TokenMetadata} and then a set is build by fetching DC name of each peer. + * @return a set of valid DC names + */ + public static Set<String> getValidDatacenters() + { + final Set<String> validDataCenters = new HashSet<>(); + final IEndpointSnitch snitch = DatabaseDescriptor.getEndpointSnitch(); + + // Add data center of localhost. + validDataCenters.add(thisDatacenter()); + // Fetch and add DCs of all peers. + for (InetAddressAndPort peer : StorageService.instance.getTokenMetadata().getAllEndpoints()) + { + validDataCenters.add(snitch.getDatacenter(peer)); + } + + return validDataCenters; + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/locator/NetworkTopologyStrategy.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/locator/NetworkTopologyStrategy.java b/src/java/org/apache/cassandra/locator/NetworkTopologyStrategy.java index 673c018..cb2ea46 100644 --- a/src/java/org/apache/cassandra/locator/NetworkTopologyStrategy.java +++ b/src/java/org/apache/cassandra/locator/NetworkTopologyStrategy.java @@ -23,11 +23,10 @@ import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.dht.Datacenters; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.dht.Token; import org.apache.cassandra.locator.TokenMetadata.Topology; -import org.apache.cassandra.service.StorageService; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.Pair; @@ -215,31 +214,10 @@ public class NetworkTopologyStrategy extends AbstractReplicationStrategy return datacenters.keySet(); } - /* - * (non-javadoc) Method to generate list of valid data center names to be used to validate the replication parameters during CREATE / ALTER keyspace operations. - * All peers of current node are fetched from {@link TokenMetadata} and then a set is build by fetching DC name of each peer. - * @return a set of valid DC names - */ - private static Set<String> buildValidDataCentersSet() - { - final Set<String> validDataCenters = new HashSet<>(); - final IEndpointSnitch snitch = DatabaseDescriptor.getEndpointSnitch(); - - // Add data center of localhost. - validDataCenters.add(snitch.getDatacenter(FBUtilities.getBroadcastAddressAndPort())); - // Fetch and add DCs of all peers. - for (final InetAddressAndPort peer : StorageService.instance.getTokenMetadata().getAllEndpoints()) - { - validDataCenters.add(snitch.getDatacenter(peer)); - } - - return validDataCenters; - } - public Collection<String> recognizedOptions() { // only valid options are valid DC names. - return buildValidDataCentersSet(); + return Datacenters.getValidDatacenters(); } protected void validateExpectedOptions() throws ConfigurationException http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/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 3e2be80..045cc8c 100644 --- a/src/java/org/apache/cassandra/service/ClientState.java +++ b/src/java/org/apache/cassandra/service/ClientState.java @@ -435,7 +435,13 @@ public class ClientState public void validateLogin() throws UnauthorizedException { if (user == null) + { throw new UnauthorizedException("You have not logged in"); + } + else if (!user.hasLocalAccess()) + { + throw new UnauthorizedException("You do not have access to this datacenter"); + } } public void ensureNotAnonymous() throws UnauthorizedException http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/service/StorageService.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index 5ea8c75..8db268d 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -1093,6 +1093,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE DatabaseDescriptor.getRoleManager().setup(); DatabaseDescriptor.getAuthenticator().setup(); DatabaseDescriptor.getAuthorizer().setup(); + DatabaseDescriptor.getNetworkAuthorizer().setup(); Schema.instance.registerListener(new AuthSchemaChangeListener()); authSetupComplete = true; } http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/src/java/org/apache/cassandra/utils/FBUtilities.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/utils/FBUtilities.java b/src/java/org/apache/cassandra/utils/FBUtilities.java index 0d6383d..078b414 100644 --- a/src/java/org/apache/cassandra/utils/FBUtilities.java +++ b/src/java/org/apache/cassandra/utils/FBUtilities.java @@ -39,8 +39,10 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.auth.AllowAllNetworkAuthorizer; import org.apache.cassandra.auth.IAuthenticator; import org.apache.cassandra.auth.IAuthorizer; +import org.apache.cassandra.auth.INetworkAuthorizer; import org.apache.cassandra.auth.IRoleManager; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.DecoratedKey; @@ -512,6 +514,19 @@ public class FBUtilities return FBUtilities.construct(className, "role manager"); } + public static INetworkAuthorizer newNetworkAuthorizer(String className) + { + if (className == null) + { + return new AllowAllNetworkAuthorizer(); + } + if (!className.contains(".")) + { + className = "org.apache.cassandra.auth." + className; + } + return FBUtilities.construct(className, "network authorizer"); + } + /** * @return The Class for the given name. * @param classname Fully qualified classname. http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/test/unit/org/apache/cassandra/SchemaLoader.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/SchemaLoader.java b/test/unit/org/apache/cassandra/SchemaLoader.java index 0633fb5..d703bab 100644 --- a/test/unit/org/apache/cassandra/SchemaLoader.java +++ b/test/unit/org/apache/cassandra/SchemaLoader.java @@ -21,6 +21,12 @@ import java.io.File; import java.io.IOException; import java.util.*; +import org.apache.cassandra.auth.AuthKeyspace; +import org.apache.cassandra.auth.AuthSchemaChangeListener; +import org.apache.cassandra.auth.IAuthenticator; +import org.apache.cassandra.auth.IAuthorizer; +import org.apache.cassandra.auth.INetworkAuthorizer; +import org.apache.cassandra.auth.IRoleManager; import org.apache.cassandra.cql3.statements.CreateTableStatement; import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.index.sasi.SASIIndex; @@ -272,6 +278,20 @@ public class SchemaLoader MigrationManager.announceNewKeyspace(KeyspaceMetadata.create(name, params, tables, Views.none(), types, Functions.none()), true); } + public static void setupAuth(IRoleManager roleManager, IAuthenticator authenticator, IAuthorizer authorizer, INetworkAuthorizer networkAuthorizer) + { + DatabaseDescriptor.setRoleManager(roleManager); + DatabaseDescriptor.setAuthenticator(authenticator); + DatabaseDescriptor.setAuthorizer(authorizer); + DatabaseDescriptor.setNetworkAuthorizer(networkAuthorizer); + MigrationManager.announceNewKeyspace(AuthKeyspace.metadata(), true); + DatabaseDescriptor.getRoleManager().setup(); + DatabaseDescriptor.getAuthenticator().setup(); + DatabaseDescriptor.getAuthorizer().setup(); + DatabaseDescriptor.getNetworkAuthorizer().setup(); + Schema.instance.registerListener(new AuthSchemaChangeListener()); + } + public static ColumnMetadata integerColumn(String ksName, String cfName) { return new ColumnMetadata(ksName, http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java b/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java new file mode 100644 index 0000000..6948203 --- /dev/null +++ b/test/unit/org/apache/cassandra/auth/CassandraNetworkAuthorizerTest.java @@ -0,0 +1,259 @@ +/* + * 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 com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.cassandra.SchemaLoader; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.cql3.statements.AlterRoleStatement; +import org.apache.cassandra.cql3.statements.AuthenticationStatement; +import org.apache.cassandra.cql3.statements.BatchStatement; +import org.apache.cassandra.cql3.statements.CreateRoleStatement; +import org.apache.cassandra.cql3.statements.DropRoleStatement; +import org.apache.cassandra.cql3.statements.SelectStatement; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.exceptions.RequestValidationException; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.messages.ResultMessage; + +import static org.apache.cassandra.auth.AuthKeyspace.NETWORK_PERMISSIONS; +import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME; + +public class CassandraNetworkAuthorizerTest +{ + private static class LocalCassandraAuthorizer extends CassandraAuthorizer + { + ResultMessage.Rows select(SelectStatement statement, QueryOptions options) + { + return statement.executeInternal(QueryState.forInternalCalls(), options); + } + + UntypedResultSet process(String query) throws RequestExecutionException + { + return QueryProcessor.executeInternal(query); + } + + @Override + void processBatch(BatchStatement statement) + { + statement.executeInternal(QueryState.forInternalCalls(), QueryOptions.DEFAULT); + } + } + + private static class LocalCassandraRoleManager extends CassandraRoleManager + { + ResultMessage.Rows select(SelectStatement statement, QueryOptions options) + { + return statement.executeInternal(QueryState.forInternalCalls(), options); + } + + UntypedResultSet process(String query, ConsistencyLevel consistencyLevel) throws RequestValidationException, RequestExecutionException + { + return QueryProcessor.executeInternal(query); + } + } + + private static class LocalCassandraNetworkAuthorizer extends CassandraNetworkAuthorizer + { + ResultMessage.Rows select(SelectStatement statement, QueryOptions options) + { + return statement.executeInternal(QueryState.forInternalCalls(), options); + } + + void process(String query) + { + QueryProcessor.executeInternal(query); + } + } + + private static void setupSuperUser() + { + QueryProcessor.executeInternal(String.format("INSERT INTO %s.%s (role, is_superuser, can_login, salted_hash) " + + "VALUES ('%s', true, true, '%s')", + AUTH_KEYSPACE_NAME, + AuthKeyspace.ROLES, + CassandraRoleManager.DEFAULT_SUPERUSER_NAME, + "xxx")); + } + + @BeforeClass + public static void defineSchema() throws ConfigurationException + { + SchemaLoader.prepareServer(); + SchemaLoader.setupAuth(new LocalCassandraRoleManager(), + new PasswordAuthenticator(), + new LocalCassandraAuthorizer(), + new LocalCassandraNetworkAuthorizer()); + setupSuperUser(); + } + + @Before + public void clear() throws Exception + { + Keyspace.open(AUTH_KEYSPACE_NAME).getColumnFamilyStore(NETWORK_PERMISSIONS).truncateBlocking(); + } + + + private static UntypedResultSet query(String q) + { + return QueryProcessor.executeInternal(q); + } + + private static void assertNoDcPermRow(String username) + { + String query = String.format("SELECT dcs FROM %s.%s WHERE role = '%s'", + AUTH_KEYSPACE_NAME, + NETWORK_PERMISSIONS, + RoleResource.role(username).getName()); + UntypedResultSet results = QueryProcessor.executeInternal(query); + Assert.assertTrue(results.isEmpty()); + } + + private static void assertDcPermRow(String username, String... dcs) + { + Set<String> expected = Sets.newHashSet(dcs); + String query = String.format("SELECT dcs FROM %s.%s WHERE role = '%s'", + AUTH_KEYSPACE_NAME, + NETWORK_PERMISSIONS, + RoleResource.role(username).getName()); + UntypedResultSet results = QueryProcessor.executeInternal(query); + UntypedResultSet.Row row = Iterables.getOnlyElement(results); + Set<String> actual = row.getFrozenSet("dcs", UTF8Type.instance); + Assert.assertEquals(expected, actual); + } + + private static String createName() + { + return RandomStringUtils.randomAlphabetic(8).toLowerCase(); + } + + private static ClientState getClientState() + { + ClientState state = ClientState.forInternalCalls(); + state.login(new AuthenticatedUser(CassandraRoleManager.DEFAULT_SUPERUSER_NAME)); + return state; + } + + private static void auth(String query, Object... args) + { + CQLStatement statement = QueryProcessor.parseStatement(String.format(query, args)).prepare().statement; + assert statement instanceof CreateRoleStatement + || statement instanceof AlterRoleStatement + || statement instanceof DropRoleStatement; + AuthenticationStatement authStmt = (AuthenticationStatement) statement; + authStmt.execute(getClientState()); + } + + private static DCPermissions dcPerms(String username) + { + AuthenticatedUser user = new AuthenticatedUser(username); + return DatabaseDescriptor.getNetworkAuthorizer().authorize(user.getPrimaryRole()); + } + + @Test + public void create() throws Exception + { + String username = createName(); + + // user should implicitly have access to all datacenters + assertNoDcPermRow(username); + auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true AND ACCESS TO DATACENTERS {'dc1', 'dc2'}", username); + Assert.assertEquals(DCPermissions.subset("dc1", "dc2"), dcPerms(username)); + assertDcPermRow(username, "dc1", "dc2"); + } + + @Test + public void alter() throws Exception + { + + String username = createName(); + + assertNoDcPermRow(username); + // user should implicitly have access to all datacenters + auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true", username); + Assert.assertEquals(DCPermissions.all(), dcPerms(username)); + assertNoDcPermRow(username); + + // unless explicitly restricted + auth("ALTER ROLE %s WITH ACCESS TO DATACENTERS {'dc1', 'dc2'}", username); + Assert.assertEquals(DCPermissions.subset("dc1", "dc2"), dcPerms(username)); + assertDcPermRow(username, "dc1", "dc2"); + + auth("ALTER ROLE %s WITH ACCESS TO DATACENTERS {'dc1'}", username); + Assert.assertEquals(DCPermissions.subset("dc1"), dcPerms(username)); + assertDcPermRow(username, "dc1"); + + auth("ALTER ROLE %s WITH ACCESS TO ALL DATACENTERS", username); + Assert.assertEquals(DCPermissions.all(), dcPerms(username)); + assertDcPermRow(username); + } + + @Test + public void drop() + { + String username = createName(); + + assertNoDcPermRow(username); + // user should implicitly have access to all datacenters + auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true AND ACCESS TO DATACENTERS {'dc1'}", username); + assertDcPermRow(username, "dc1"); + + auth("DROP ROLE %s", username); + assertNoDcPermRow(username); + } + + @Test + public void superUser() throws Exception + { + String username = createName(); + auth("CREATE ROLE %s WITH password = 'password' AND LOGIN = true AND ACCESS TO DATACENTERS {'dc1'}", username); + Assert.assertEquals(DCPermissions.subset("dc1"), dcPerms(username)); + assertDcPermRow(username, "dc1"); + + auth("ALTER ROLE %s WITH superuser = true", username); + Assert.assertEquals(DCPermissions.all(), dcPerms(username)); + } + + @Test + public void cantLogin() throws Exception + { + String username = createName(); + auth("CREATE ROLE %s", username); + Assert.assertEquals(DCPermissions.none(), dcPerms(username)); + + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java index dd45f72..084ad7e 100644 --- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java +++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java @@ -59,6 +59,7 @@ public class DatabaseDescriptorRefTest "org.apache.cassandra.auth.IAuthenticator", "org.apache.cassandra.auth.IAuthorizer", "org.apache.cassandra.auth.IRoleManager", + "org.apache.cassandra.auth.INetworkAuthorizer", "org.apache.cassandra.config.DatabaseDescriptor", "org.apache.cassandra.config.ConfigurationLoader", "org.apache.cassandra.config.Config", http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/test/unit/org/apache/cassandra/cql3/statements/AlterRoleStatementTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/statements/AlterRoleStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/AlterRoleStatementTest.java new file mode 100644 index 0000000..77e5236 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/statements/AlterRoleStatementTest.java @@ -0,0 +1,73 @@ +/* + * 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.cql3.statements; + +import com.google.common.collect.Sets; +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.auth.DCPermissions; +import org.apache.cassandra.cql3.QueryProcessor; + +public class AlterRoleStatementTest +{ + private static AlterRoleStatement parse(String query) + { + ParsedStatement stmt = QueryProcessor.parseStatement(query); + Assert.assertTrue(stmt instanceof AlterRoleStatement); + return (AlterRoleStatement) stmt; + } + + private static DCPermissions dcPerms(String query) + { + return parse(query).dcPermissions; + } + + @Test + public void dcsNotSpecified() throws Exception + { + Assert.assertNull(dcPerms("ALTER ROLE r1 WITH PASSWORD = 'password'")); + } + + @Test + public void dcsAllSpecified() throws Exception + { + DCPermissions dcPerms = dcPerms("ALTER ROLE r1 WITH ACCESS TO ALL DATACENTERS"); + Assert.assertNotNull(dcPerms); + Assert.assertFalse(dcPerms.restrictsAccess()); + } + + @Test + public void singleDc() throws Exception + { + DCPermissions dcPerms = dcPerms("ALTER ROLE r1 WITH ACCESS TO DATACENTERS {'dc1'}"); + Assert.assertNotNull(dcPerms); + Assert.assertTrue(dcPerms.restrictsAccess()); + Assert.assertEquals(Sets.newHashSet("dc1"), dcPerms.allowedDCs()); + } + + @Test + public void multiDcs() throws Exception + { + DCPermissions dcPerms = dcPerms("ALTER ROLE r1 WITH ACCESS TO DATACENTERS {'dc1', 'dc2'}"); + Assert.assertNotNull(dcPerms); + Assert.assertTrue(dcPerms.restrictsAccess()); + Assert.assertEquals(Sets.newHashSet("dc1", "dc2"), dcPerms.allowedDCs()); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/test/unit/org/apache/cassandra/cql3/statements/CreateRoleStatementTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/statements/CreateRoleStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/CreateRoleStatementTest.java new file mode 100644 index 0000000..0ff26b5 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/statements/CreateRoleStatementTest.java @@ -0,0 +1,72 @@ +/* + * 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.cql3.statements; + +import com.google.common.collect.Sets; +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.auth.DCPermissions; +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.cql3.QueryProcessor; + +public class CreateRoleStatementTest extends CQLTester +{ + + private static CreateRoleStatement parse(String query) + { + ParsedStatement stmt = QueryProcessor.parseStatement(query); + Assert.assertTrue(stmt instanceof CreateRoleStatement); + return (CreateRoleStatement) stmt; + } + + private static DCPermissions dcPerms(String query) + { + return parse(query).dcPermissions; + } + + @Test + public void allDcsImplicit() throws Exception + { + Assert.assertFalse(dcPerms("CREATE ROLE role").restrictsAccess()); + } + + @Test + public void allDcsExplicit() throws Exception + { + Assert.assertFalse(dcPerms("CREATE ROLE role WITH ACCESS TO ALL DATACENTERS").restrictsAccess()); + } + + @Test + public void singleDc() throws Exception + { + DCPermissions perms = dcPerms("CREATE ROLE role WITH ACCESS TO DATACENTERS {'dc1'}"); + Assert.assertTrue(perms.restrictsAccess()); + Assert.assertEquals(Sets.newHashSet("dc1"), perms.allowedDCs()); + } + + @Test + public void multiDcs() throws Exception + { + DCPermissions perms = dcPerms("CREATE ROLE role WITH ACCESS TO DATACENTERS {'dc1', 'dc2'}"); + Assert.assertTrue(perms.restrictsAccess()); + Assert.assertEquals(Sets.newHashSet("dc1", "dc2"), perms.allowedDCs()); + + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java new file mode 100644 index 0000000..51e38b3 --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/statements/CreateUserStatementTest.java @@ -0,0 +1,46 @@ +/* + * 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.cql3.statements; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.auth.DCPermissions; +import org.apache.cassandra.cql3.QueryProcessor; + +public class CreateUserStatementTest +{ + private static CreateRoleStatement parse(String query) + { + ParsedStatement stmt = QueryProcessor.parseStatement(query); + Assert.assertTrue(stmt instanceof CreateRoleStatement); + return (CreateRoleStatement) stmt; + } + + private static DCPermissions dcPerms(String query) + { + return parse(query).dcPermissions; + } + + @Test + public void allDcsImplicit() throws Exception + { + Assert.assertFalse(dcPerms("CREATE USER u1").restrictsAccess()); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/54de771e/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java index dc90b4e..96f88c3 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/CreateTest.java @@ -40,6 +40,7 @@ import org.apache.cassandra.locator.AbstractEndpointSnitch; import org.apache.cassandra.locator.IEndpointSnitch; import org.apache.cassandra.io.util.DataOutputBuffer; import org.apache.cassandra.schema.SchemaKeyspace; +import org.apache.cassandra.service.StorageService; import org.apache.cassandra.triggers.ITrigger; import org.apache.cassandra.utils.ByteBufferUtil; @@ -49,7 +50,6 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.apache.cassandra.cql3.Duration.*; -import static org.junit.Assert.assertEquals; public class CreateTest extends CQLTester { @@ -523,6 +523,9 @@ public class CreateTest extends CQLTester public int compareEndpoints(InetAddressAndPort target, InetAddressAndPort a1, InetAddressAndPort a2) { return 0; } }); + // this forces the dc above to be added to the list of known datacenters (fixes static init problem + // with this group of tests), ok to remove at some point if doing so doesn't break the test + StorageService.instance.getTokenMetadata().updateHostId(UUID.randomUUID(), InetAddressAndPort.getByName("127.0.0.255")); execute("CREATE KEYSPACE Foo WITH replication = { 'class' : 'NetworkTopologyStrategy', 'us-east-1' : 1 };"); // Restore the previous EndpointSnitch --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org