From 0b83682b40d3bace7bc0d604db46ddec78f2cd4a Mon Sep 17 00:00:00 2001 From: Aleksey Yeschenko Date: Fri, 15 Feb 2013 01:26:10 +0300 Subject: [PATCH] Add CQL3-based implementations of IAuthenticator and IAuthorizer Patch by Aleksey Yeschenko; reviewed by Jonathan Ellis for CASSANDRA-4898 --- CHANGES.txt | 2 + NEWS.txt | 17 ++ build.xml | 3 +- conf/cassandra.yaml | 22 +- examples/simple_authentication/README.txt | 25 -- .../conf/access.properties | 39 --- .../conf/passwd.properties | 24 -- .../cassandra/auth/SimpleAuthenticator.java | 142 ---------- .../cassandra/auth/SimpleAuthorizer.java | 157 ----------- lib/jbcrypt-0.3m.jar | Bin 0 -> 17750 bytes lib/licenses/jbcrypt-0.3m.txt | 17 ++ src/java/org/apache/cassandra/auth/Auth.java | 8 +- .../cassandra/auth/CassandraAuthorizer.java | 254 ++++++++++++++++++ .../apache/cassandra/auth/IAuthenticator.java | 20 +- .../apache/cassandra/auth/IAuthorizer.java | 24 +- .../cassandra/auth/LegacyAuthenticator.java | 14 +- .../cassandra/auth/LegacyAuthorizer.java | 4 +- .../cassandra/auth/PasswordAuthenticator.java | 223 +++++++++++++++ .../apache/cassandra/cli/CliSessionState.java | 4 +- .../cql3/statements/AlterUserStatement.java | 15 +- .../cql3/statements/CreateUserStatement.java | 12 +- .../cql3/statements/DropUserStatement.java | 9 +- .../cql3/statements/GrantStatement.java | 6 +- .../statements/ListPermissionsStatement.java | 16 +- .../PermissionAlteringStatement.java | 22 +- .../cql3/statements/RevokeStatement.java | 6 +- .../apache/cassandra/service/ClientState.java | 5 +- test/conf/access.properties | 29 -- 28 files changed, 630 insertions(+), 489 deletions(-) delete mode 100644 examples/simple_authentication/README.txt delete mode 100644 examples/simple_authentication/conf/access.properties delete mode 100644 examples/simple_authentication/conf/passwd.properties delete mode 100644 examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthenticator.java delete mode 100644 examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthorizer.java create mode 100644 lib/jbcrypt-0.3m.jar create mode 100644 lib/licenses/jbcrypt-0.3m.txt create mode 100644 src/java/org/apache/cassandra/auth/CassandraAuthorizer.java create mode 100644 src/java/org/apache/cassandra/auth/PasswordAuthenticator.java delete mode 100644 test/conf/access.properties diff --git a/CHANGES.txt b/CHANGES.txt index 39b1948ec4..8ecf5946cb 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -20,6 +20,8 @@ * Simplify auth setup and make system_auth ks alterable (CASSANDRA-5112) * Stop compactions from hanging during bootstrap (CASSANDRA-5244) * fix compressed streaming sending extra chunk (CASSANDRA-5105) + * Add CQL3-based implementations of IAuthenticator and IAuthorizer + (CASSANDRA-4898) 1.2.1 diff --git a/NEWS.txt b/NEWS.txt index 7e04b2ef85..5b4f478a49 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -21,6 +21,23 @@ Upgrading favor of blob constants) and its support will be removed in a future version. +Features +-------- + - Built-in CQL3-based implementations of IAuthenticator (PasswordAuthenticator) + and IAuthorizer (CassandraAuthorizer) have been added. PasswordAuthenticator + stores usernames and hashed passwords in system_auth.credentials table; + CassandraAuthorizer stores permissions in system_auth.permissions table. + - system_auth keyspace is now alterable via ALTER KEYSPACE queries. + The default is SimpleStrategy with replication_factor of 1, but it's + advised to raise RF to at least 3 or 5, since CL.QUORUM is used for all + auth-related queries. It's also possible to change the strategy to NTS. + - Permissions caching with time-based expiration policy has been added to reduce + performance impact of authorization. Permission validity can be configured + using 'permissions_validity_in_ms' setting in cassandra.yaml. The default + is 2000 (2 seconds). + - SimpleAuthenticator and SimpleAuthorizer examples have been removed. Please + look at CassandraAuthorizer/PasswordAuthenticator instead. + 1.2.1 ===== diff --git a/build.xml b/build.xml index ea0bbd72a7..1fd2bf92b4 100644 --- a/build.xml +++ b/build.xml @@ -379,6 +379,7 @@ + @@ -461,6 +462,7 @@ + @@ -1041,7 +1043,6 @@ - diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index f027c15ae0..8e910d841c 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -54,15 +54,29 @@ max_hints_delivery_threads: 2 # Defaults to: false # populate_io_cache_on_flush: false -# authentication backend, implementing IAuthenticator; used to identify users +# Authentication backend, implementing IAuthenticator; used to identify users +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator, +# PasswordAuthenticator}. +# +# - AllowAllAuthenticator performs no checks - set it to disable authentication. +# - PasswordAuthenticator relies on username/password pairs to authenticate +# users. It keeps usernames and hashed passwords in system_auth.credentials table. +# Please increase system_auth keyspace replication factor if you use this authenticator. authenticator: org.apache.cassandra.auth.AllowAllAuthenticator -# authorization backend, implementing IAuthorizer; used to limit access/provide permissions +# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions +# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, +# CassandraAuthorizer}. +# +# - AllowAllAuthorizer allows any action to any user - set it to disable authorization. +# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please +# increase system_auth keyspace replication factor if you use this authorizer. authorizer: org.apache.cassandra.auth.AllowAllAuthorizer # Validity period for permissions cache (fetching permissions can be an -# expensive operation depending on the authorizer). Defaults to 2000, -# set to 0 to disable. Will be disabled automatically for AllowAllAuthorizer. +# expensive operation depending on the authorizer, CassandraAuthorizer is +# one example). Defaults to 2000, set to 0 to disable. +# Will be disabled automatically for AllowAllAuthorizer. permissions_validity_in_ms: 2000 # The partitioner is responsible for distributing rows (by key) across diff --git a/examples/simple_authentication/README.txt b/examples/simple_authentication/README.txt deleted file mode 100644 index f81f5e782d..0000000000 --- a/examples/simple_authentication/README.txt +++ /dev/null @@ -1,25 +0,0 @@ -The files in this directory provide a (simplistic) example of how to add -authentication and resource permissions to Cassandra by implementing the -org.apache.cassandra.auth.{IAuthenticator, IAuthorizer} interfaces. - -To try those examples, copy the two JAVA sources (in src/) into the main -cassandra sources directory and the two configuration files (in conf/) in the -main cassandra configuration directory. - -You can then set the authenticator and authorizer properties in cassandra.yaml -to use those classes. See the two configuration files access.properties and -passwd.properties to configure the authorized users and permissions. - -When starting cassandra, you need to specify the location of the passwd.properties -and access.properties files by adding JVM args similar to the following either -in cassandra-env.sh or as commandline arguments: - - -Dpasswd.properties=conf/passwd.properties - -Daccess.properties=conf/access.properties - -For example, you might invoke cassandra as follows: - - bin/cassandra -f -Dpasswd.properties=conf/passwd.properties -Daccess.properties=conf/access.properties - -Please note that the code in this directory is for demonstration purposes. In -particular, it does not provide a high level of security. diff --git a/examples/simple_authentication/conf/access.properties b/examples/simple_authentication/conf/access.properties deleted file mode 100644 index f403c2fd09..0000000000 --- a/examples/simple_authentication/conf/access.properties +++ /dev/null @@ -1,39 +0,0 @@ -# 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. - -# This is a sample access file for SimpleAuthorizer. The format of this file -# is KEYSPACE[.COLUMNFAMILY].PERMISSION=USERS, where: -# -# * KEYSPACE is the keyspace name. -# * COLUMNFAMILY is the column family name. -# * PERMISSION is one of or for read-only or read-write respectively. -# * USERS is a comma delimited list of users from passwd.properties. -# -# See below for example entries. - -# NOTE: This file contains potentially sensitive information, please keep -# this in mind when setting its mode and ownership. - -# The magical '' property lists users who can modify the -# list of keyspaces: all users will be able to view the list of keyspaces. -=jsmith - -# Access to Keyspace1 (add/remove column families, etc). -Keyspace1.=jsmith,Elvis Presley -Keyspace1.=dilbert - -# Access to Standard1 (keyspace Keyspace1) -Keyspace1.Standard1.=jsmith,Elvis Presley,dilbert diff --git a/examples/simple_authentication/conf/passwd.properties b/examples/simple_authentication/conf/passwd.properties deleted file mode 100644 index 3099ba6b94..0000000000 --- a/examples/simple_authentication/conf/passwd.properties +++ /dev/null @@ -1,24 +0,0 @@ -# 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. -# -# This is a sample password file for SimpleAuthenticator. The format of -# this file is username=password. If -Dpasswd.mode=MD5 then the password -# is represented as an md5 digest, otherwise it is cleartext (keep this -# in mind when setting file mode and ownership). 'cassandra' is the default -# superuser and can be removed later. -cassandra=cassandra -jsmith=havebadpass -dilbert=nomoovertime diff --git a/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthenticator.java b/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthenticator.java deleted file mode 100644 index 0248244e47..0000000000 --- a/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthenticator.java +++ /dev/null @@ -1,142 +0,0 @@ -package org.apache.cassandra.auth; -/* - * - * 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. - * - */ - - -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.MessageDigest; -import java.util.Map; -import java.util.Properties; - -import org.apache.cassandra.exceptions.AuthenticationException; -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.cassandra.io.util.FileUtils; -import org.apache.cassandra.utils.FBUtilities; -import org.apache.cassandra.utils.Hex; - -public class SimpleAuthenticator extends LegacyAuthenticator -{ - public final static String PASSWD_FILENAME_PROPERTY = "passwd.properties"; - public final static String PMODE_PROPERTY = "passwd.mode"; - - public enum PasswordMode - { - PLAIN, MD5, - } - - public AuthenticatedUser defaultUser() - { - // users must log in - return null; - } - - public AuthenticatedUser authenticate(Map credentials) throws AuthenticationException - { - String pmode_plain = System.getProperty(PMODE_PROPERTY); - PasswordMode mode = PasswordMode.PLAIN; - - if (pmode_plain != null) - { - try - { - mode = PasswordMode.valueOf(pmode_plain); - } - catch (Exception e) - { - // this is not worth a StringBuffer - String mode_values = ""; - for (PasswordMode pm : PasswordMode.values()) - mode_values += "'" + pm + "', "; - - mode_values += "or leave it unspecified."; - throw new AuthenticationException("The requested password check mode '" + pmode_plain + "' is not a valid mode. Possible values are " + mode_values); - } - } - - String pfilename = System.getProperty(PASSWD_FILENAME_PROPERTY); - - String username = credentials.get(USERNAME_KEY); - if (username == null) - throw new AuthenticationException("Authentication request was missing the required key '" + USERNAME_KEY + "'"); - - String password = credentials.get(PASSWORD_KEY); - if (password == null) - throw new AuthenticationException("Authentication request was missing the required key '" + PASSWORD_KEY + "'"); - - boolean authenticated = false; - - InputStream in = null; - try - { - in = new BufferedInputStream(new FileInputStream(pfilename)); - Properties props = new Properties(); - props.load(in); - - // note we keep the message here and for the wrong password exactly the same to prevent attackers from guessing what users are valid - if (props.getProperty(username) == null) throw new AuthenticationException(authenticationErrorMessage(mode, username)); - switch (mode) - { - case PLAIN: - authenticated = password.equals(props.getProperty(username)); - break; - case MD5: - authenticated = MessageDigest.isEqual(FBUtilities.threadLocalMD5Digest().digest(password.getBytes()), Hex.hexToBytes(props.getProperty(username))); - break; - default: - throw new RuntimeException("Unknown PasswordMode " + mode); - } - } - catch (IOException e) - { - throw new RuntimeException("Authentication table file given by property " + PASSWD_FILENAME_PROPERTY + " could not be opened: " + e.getMessage()); - } - catch (Exception e) - { - throw new RuntimeException("Unexpected authentication problem", e); - } - finally - { - FileUtils.closeQuietly(in); - } - - if (!authenticated) throw new AuthenticationException(authenticationErrorMessage(mode, username)); - - return new AuthenticatedUser(username); - } - - public void validateConfiguration() throws ConfigurationException - { - String pfilename = System.getProperty(SimpleAuthenticator.PASSWD_FILENAME_PROPERTY); - if (pfilename == null) - { - throw new ConfigurationException("When using " + this.getClass().getCanonicalName() + " " + - SimpleAuthenticator.PASSWD_FILENAME_PROPERTY + " properties must be defined."); - } - } - - static String authenticationErrorMessage(PasswordMode mode, String username) - { - return String.format("Given password in password mode %s could not be validated for user %s", mode, username); - } -} diff --git a/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthorizer.java b/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthorizer.java deleted file mode 100644 index df626547a6..0000000000 --- a/examples/simple_authentication/src/org/apache/cassandra/auth/SimpleAuthorizer.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.apache.cassandra.auth; -/* - * - * 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. - * - */ - - -import java.io.BufferedInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.EnumSet; -import java.util.List; -import java.util.Properties; - -import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.cassandra.io.util.FileUtils; - -public class SimpleAuthorizer extends LegacyAuthorizer -{ - public final static String ACCESS_FILENAME_PROPERTY = "access.properties"; - // magical property for WRITE permissions to the keyspaces list - public final static String KEYSPACES_WRITE_PROPERTY = ""; - - public EnumSet authorize(AuthenticatedUser user, List resource) - { - if (resource.size() < 2 || !Resources.ROOT.equals(resource.get(0)) || !Resources.KEYSPACES.equals(resource.get(1))) - return EnumSet.noneOf(Permission.class); - - String keyspace, columnFamily = null; - EnumSet authorized = EnumSet.noneOf(Permission.class); - - // /cassandra/keyspaces - if (resource.size() == 2) - { - keyspace = KEYSPACES_WRITE_PROPERTY; - authorized = EnumSet.of(Permission.READ); - } - // /cassandra/keyspaces/ - else if (resource.size() == 3) - { - keyspace = (String)resource.get(2); - } - // /cassandra/keyspaces// - else if (resource.size() == 4) - { - keyspace = (String)resource.get(2); - columnFamily = (String)resource.get(3); - } - else - { - // We don't currently descend any lower in the hierarchy. - throw new UnsupportedOperationException(); - } - - String accessFilename = System.getProperty(ACCESS_FILENAME_PROPERTY); - InputStream in=null; - try - { - in = new BufferedInputStream(new FileInputStream(accessFilename)); - Properties accessProperties = new Properties(); - accessProperties.load(in); - - // Special case access to the keyspace list - if (keyspace == KEYSPACES_WRITE_PROPERTY) - { - String kspAdmins = accessProperties.getProperty(KEYSPACES_WRITE_PROPERTY); - for (String admin : kspAdmins.split(",")) - if (admin.equals(user.getName())) - return EnumSet.copyOf(Permission.ALL); - } - - boolean canRead = false, canWrite = false; - String readers = null, writers = null; - - if (columnFamily == null) - { - readers = accessProperties.getProperty(keyspace + "."); - writers = accessProperties.getProperty(keyspace + "."); - } - else - { - readers = accessProperties.getProperty(keyspace + "." + columnFamily + "."); - writers = accessProperties.getProperty(keyspace + "." + columnFamily + "."); - } - - if (readers != null) - { - for (String reader : readers.split(",")) - { - if (reader.equals(user.getName())) - { - canRead = true; - break; - } - } - } - - if (writers != null) - { - for (String writer : writers.split(",")) - { - if (writer.equals(user.getName())) - { - canWrite = true; - break; - } - } - } - - if (canWrite) - authorized = EnumSet.copyOf(Permission.ALL); - else if (canRead) - authorized = EnumSet.of(Permission.READ); - - } - catch (IOException e) - { - throw new RuntimeException(String.format("Authorization table file '%s' could not be opened: %s", - accessFilename, - e.getMessage())); - } - finally - { - FileUtils.closeQuietly(in); - } - - return authorized; - } - - public void validateConfiguration() throws ConfigurationException - { - String afilename = System.getProperty(ACCESS_FILENAME_PROPERTY); - if (afilename == null) - { - throw new ConfigurationException(String.format("When using %s, '%s' property must be defined.", - this.getClass().getCanonicalName(), - ACCESS_FILENAME_PROPERTY)); - } - } -} diff --git a/lib/jbcrypt-0.3m.jar b/lib/jbcrypt-0.3m.jar new file mode 100644 index 0000000000000000000000000000000000000000..ccace8bbcd563e371a9d09bf25355acb053434b2 GIT binary patch literal 17750 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 BJZ}I1 literal 0 HcmV?d00001 diff --git a/lib/licenses/jbcrypt-0.3m.txt b/lib/licenses/jbcrypt-0.3m.txt new file mode 100644 index 0000000000..d332534c06 --- /dev/null +++ b/lib/licenses/jbcrypt-0.3m.txt @@ -0,0 +1,17 @@ +jBCrypt is subject to the following license: + +/* + * Copyright (c) 2006 Damien Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/src/java/org/apache/cassandra/auth/Auth.java b/src/java/org/apache/cassandra/auth/Auth.java index 399dc26f75..43118e45c3 100644 --- a/src/java/org/apache/cassandra/auth/Auth.java +++ b/src/java/org/apache/cassandra/auth/Auth.java @@ -100,6 +100,7 @@ public class Auth * * @param username Username to insert. * @param isSuper User's new status. + * @throws RequestExecutionException */ public static void insertUser(String username, boolean isSuper) throws RequestExecutionException { @@ -115,6 +116,7 @@ public class Auth * Deletes the user from AUTH_KS.USERS_CF. * * @param username Username to delete. + * @throws RequestExecutionException */ public static void deleteUser(String username) throws RequestExecutionException { @@ -140,7 +142,7 @@ public class Auth MigrationManager.instance.register(new MigrationListener()); // the delay is here to give the node some time to see its peers - to reduce - // "Skipping default superuser setup: some nodes are not ready" log spam. + // "Skipped default superuser setup: some nodes were not ready" log spam. // It's the only reason for the delay. StorageService.tasks.schedule(new Runnable() { @@ -191,18 +193,18 @@ public class Auth // insert a default superuser if AUTH_KS.USERS_CF is empty. if (QueryProcessor.process(String.format("SELECT * FROM %s.%s", AUTH_KS, USERS_CF), ConsistencyLevel.QUORUM).isEmpty()) { - logger.info("Creating default superuser '{}'", DEFAULT_SUPERUSER_NAME); QueryProcessor.process(String.format("INSERT INTO %s.%s (name, super) VALUES ('%s', %s) USING TIMESTAMP 0", AUTH_KS, USERS_CF, DEFAULT_SUPERUSER_NAME, true), ConsistencyLevel.QUORUM); + logger.info("Created default superuser '{}'", DEFAULT_SUPERUSER_NAME); } } catch (RequestExecutionException e) { - logger.warn("Skipping default superuser setup: some nodes are not ready"); + logger.warn("Skipped default superuser setup: some nodes were not ready"); } } diff --git a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java new file mode 100644 index 0000000000..2227e5b213 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java @@ -0,0 +1,254 @@ +/* + * 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.*; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.exceptions.*; + +/** + * CassandraAuthorizer is an IAuthorizer implementation that keeps + * permissions internally in C* - in system_auth.permissions CQL3 table. + */ +public class CassandraAuthorizer implements IAuthorizer +{ + private static final Logger logger = LoggerFactory.getLogger(CassandraAuthorizer.class); + + private static final String USERNAME = "username"; + private static final String RESOURCE = "resource"; + private static final String PERMISSIONS = "permissions"; + + private static final String PERMISSIONS_CF = "permissions"; + private static final String PERMISSIONS_CF_SCHEMA = String.format("CREATE TABLE %s.%s (" + + "username text," + + "resource text," + + "permissions set," + + "PRIMARY KEY(username, resource)" + + ") WITH gc_grace_seconds=%d", + Auth.AUTH_KS, + PERMISSIONS_CF, + 90 * 24 * 60 * 60); // 3 months. + + // Returns every permission on the resource granted to the user. + public Set authorize(AuthenticatedUser user, IResource resource) + { + if (user.isSuper()) + return Permission.ALL; + + UntypedResultSet rows; + try + { + rows = process(String.format("SELECT permissions FROM %s.%s WHERE username = '%s' AND resource = '%s'", + Auth.AUTH_KS, + PERMISSIONS_CF, + escape(user.getName()), + escape(resource.getName()))); + } + catch (RequestExecutionException e) + { + logger.warn("CassandraAuthorizer failed to authorize {} for {}", user, resource); + return Permission.NONE; + } + + if (rows.isEmpty() || !rows.one().has(PERMISSIONS)) + return Permission.NONE; + + Set permissions = EnumSet.noneOf(Permission.class); + for (String perm : rows.one().getSet(PERMISSIONS, UTF8Type.instance)) + permissions.add(Permission.valueOf(perm)); + return permissions; + } + + public void grant(AuthenticatedUser performer, Set permissions, IResource resource, String to) + throws RequestExecutionException + { + modify(permissions, resource, to, "+"); + } + + public void revoke(AuthenticatedUser performer, Set permissions, IResource resource, String from) + throws RequestExecutionException + { + modify(permissions, resource, from, "-"); + } + + // Adds or removes permissions from user's 'permissions' set (adds if op is "+", removes if op is "-") + private void modify(Set permissions, IResource resource, String user, String op) throws RequestExecutionException + { + process(String.format("UPDATE %s.%s SET permissions = permissions %s {%s} WHERE username = '%s' AND resource = '%s'", + Auth.AUTH_KS, + PERMISSIONS_CF, + op, + "'" + StringUtils.join(permissions, "','") + "'", + escape(user), + escape(resource.getName()))); + } + + // 'of' can be null - in that case everyone's permissions have been requested. Otherwise only single user's. + // If the user requesting 'LIST PERMISSIONS' is not a superuser OR his username doesn't match 'of', we + // throw UnauthorizedException. So only a superuser can view everybody's permissions. Regular users are only + // allowed to see their own permissions. + public Set list(AuthenticatedUser performer, Set permissions, IResource resource, String of) + throws RequestValidationException, RequestExecutionException + { + if (!performer.isSuper() && !performer.getName().equals(of)) + throw new UnauthorizedException(String.format("You are not authorized to view %s's permissions", + of == null ? "everyone" : of)); + + Set details = new HashSet(); + + for (UntypedResultSet.Row row : process(buildListQuery(resource, of))) + { + if (row.has(PERMISSIONS)) + { + for (String p : row.getSet(PERMISSIONS, UTF8Type.instance)) + { + Permission permission = Permission.valueOf(p); + if (permissions.contains(permission)) + details.add(new PermissionDetails(row.getString(USERNAME), + DataResource.fromName(row.getString(RESOURCE)), + permission)); + } + } + } + + return details; + } + + private static String buildListQuery(IResource resource, String of) + { + List vars = Lists.newArrayList(Auth.AUTH_KS, PERMISSIONS_CF); + List conditions = new ArrayList(); + + if (resource != null) + { + conditions.add("resource = '%s'"); + vars.add(escape(resource.getName())); + } + + if (of != null) + { + conditions.add("username = '%s'"); + vars.add(escape(of)); + } + + String query = "SELECT username, resource, permissions FROM %s.%s"; + + if (!conditions.isEmpty()) + query += " WHERE " + StringUtils.join(conditions, " AND "); + + if (resource != null && of == null) + query += " ALLOW FILTERING"; + + return String.format(query, vars.toArray()); + } + + // Called prior to deleting the user with DROP USER query. Internal hook, so no permission checks are needed here. + public void revokeAll(String droppedUser) + { + try + { + process(String.format("DELETE FROM %s.%s WHERE username = '%s'", Auth.AUTH_KS, PERMISSIONS_CF, escape(droppedUser))); + } + catch (Throwable e) + { + logger.warn("CassandraAuthorizer failed to revoke all permissions of {}: {}", droppedUser, e); + } + } + + // Called after a resource is removed (DROP KEYSPACE, DROP TABLE, etc.). + public void revokeAll(IResource droppedResource) + { + + UntypedResultSet rows; + try + { + // TODO: switch to secondary index on 'resource' once https://issues.apache.org/jira/browse/CASSANDRA-5125 is resolved. + rows = process(String.format("SELECT username FROM %s.%s WHERE resource = '%s' ALLOW FILTERING", + Auth.AUTH_KS, + PERMISSIONS_CF, + escape(droppedResource.getName()))); + } + catch (Throwable e) + { + logger.warn("CassandraAuthorizer failed to revoke all permissions on {}: {}", droppedResource, e); + return; + } + + for (UntypedResultSet.Row row : rows) + { + try + { + process(String.format("DELETE FROM %s.%s WHERE username = '%s' AND resource = '%s'", + Auth.AUTH_KS, + PERMISSIONS_CF, + escape(row.getString(USERNAME)), + escape(droppedResource.getName()))); + } + catch (Throwable e) + { + logger.warn("CassandraAuthorizer failed to revoke all permissions on {}: {}", droppedResource, e); + } + } + } + + public Set protectedResources() + { + return ImmutableSet.of(DataResource.columnFamily(Auth.AUTH_KS, PERMISSIONS_CF)); + } + + public void validateConfiguration() throws ConfigurationException + { + } + + public void setup() + { + if (Schema.instance.getCFMetaData(Auth.AUTH_KS, PERMISSIONS_CF) == null) + { + try + { + process(PERMISSIONS_CF_SCHEMA); + } + catch (RequestExecutionException e) + { + throw new AssertionError(e); + } + } + } + + // We only worry about one character ('). Make sure it's properly escaped. + private static String escape(String name) + { + return StringUtils.replace(name, "'", "''"); + } + + private static UntypedResultSet process(String query) throws RequestExecutionException + { + return QueryProcessor.process(query, ConsistencyLevel.QUORUM); + } +} diff --git a/src/java/org/apache/cassandra/auth/IAuthenticator.java b/src/java/org/apache/cassandra/auth/IAuthenticator.java index 69a9e8316f..608649078e 100644 --- a/src/java/org/apache/cassandra/auth/IAuthenticator.java +++ b/src/java/org/apache/cassandra/auth/IAuthenticator.java @@ -22,7 +22,8 @@ import java.util.Set; import org.apache.cassandra.exceptions.AuthenticationException; import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.exceptions.RequestValidationException; public interface IAuthenticator { @@ -72,9 +73,10 @@ public interface IAuthenticator * * @param username Username of the user to create. * @param options Options the user will be created with. - * @throws InvalidRequestException + * @throws RequestValidationException + * @throws RequestExecutionException */ - void create(String username, Map options) throws InvalidRequestException; + void create(String username, Map options) throws RequestValidationException, RequestExecutionException; /** * Called during execution of ALTER USER query. @@ -84,23 +86,25 @@ public interface IAuthenticator * * @param username Username of the user that will be altered. * @param options Options to alter. - * @throws InvalidRequestException + * @throws RequestValidationException + * @throws RequestExecutionException */ - void alter(String username, Map options) throws InvalidRequestException; + void alter(String username, Map options) throws RequestValidationException, RequestExecutionException; /** * Called during execution of DROP USER query. * * @param username Username of the user that will be dropped. - * @throws InvalidRequestException + * @throws RequestValidationException + * @throws RequestExecutionException */ - void drop(String username) throws InvalidRequestException; + void drop(String username) throws RequestValidationException, RequestExecutionException; /** * Set of resources that should be made inaccessible to users and only accessible internally. * - * @return Keyspaces, column families that will be unreadable and unmodifiable by users; other resources. + * @return Keyspaces, column families that will be unmodifiable by users; other resources. */ Set protectedResources(); diff --git a/src/java/org/apache/cassandra/auth/IAuthorizer.java b/src/java/org/apache/cassandra/auth/IAuthorizer.java index de1b3930b5..8ad204f073 100644 --- a/src/java/org/apache/cassandra/auth/IAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/IAuthorizer.java @@ -20,8 +20,8 @@ package org.apache.cassandra.auth; import java.util.Set; import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.cassandra.exceptions.InvalidRequestException; -import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.exceptions.RequestValidationException; /** * Primary Cassandra authorization interface. @@ -46,11 +46,11 @@ public interface IAuthorizer * @param to Grantee of the permissions. * @param resource Resource on which to grant the permissions. * - * @throws UnauthorizedException if the granting user isn't allowed to grant (and revoke) the permissions on the resource. - * @throws InvalidRequestException upon parameter misconfiguration or internal error. + * @throws RequestValidationException + * @throws RequestExecutionException */ void grant(AuthenticatedUser performer, Set permissions, IResource resource, String to) - throws UnauthorizedException, InvalidRequestException; + throws RequestValidationException, RequestExecutionException; /** * Revokes a set of permissions on a resource from a user. @@ -61,11 +61,11 @@ public interface IAuthorizer * @param from Revokee of the permissions. * @param resource Resource on which to revoke the permissions. * - * @throws UnauthorizedException if the revoking user isn't allowed to revoke the permissions on the resource. - * @throws InvalidRequestException upon parameter misconfiguration or internal error. + * @throws RequestValidationException + * @throws RequestExecutionException */ void revoke(AuthenticatedUser performer, Set permissions, IResource resource, String from) - throws UnauthorizedException, InvalidRequestException; + throws RequestValidationException, RequestExecutionException; /** * Returns a list of permissions on a resource of a user. @@ -78,11 +78,11 @@ public interface IAuthorizer * * @return All of the matching permission that the requesting user is authorized to know about. * - * @throws UnauthorizedException if the user isn't allowed to view the requested permissions. - * @throws InvalidRequestException upon parameter misconfiguration or internal error. + * @throws RequestValidationException + * @throws RequestExecutionException */ Set list(AuthenticatedUser performer, Set permissions, IResource resource, String of) - throws UnauthorizedException, InvalidRequestException; + throws RequestValidationException, RequestExecutionException; /** * This method is called before deleting a user with DROP USER query so that a new user with the same @@ -102,7 +102,7 @@ public interface IAuthorizer /** * Set of resources that should be made inaccessible to users and only accessible internally. * - * @return Keyspaces, column families that will be unreadable and unmodifiable by users; other resources. + * @return Keyspaces, column families that will be unmodifiable by users; other resources. */ Set protectedResources(); diff --git a/src/java/org/apache/cassandra/auth/LegacyAuthenticator.java b/src/java/org/apache/cassandra/auth/LegacyAuthenticator.java index 5210af54a7..c5fd8daab4 100644 --- a/src/java/org/apache/cassandra/auth/LegacyAuthenticator.java +++ b/src/java/org/apache/cassandra/auth/LegacyAuthenticator.java @@ -17,11 +17,14 @@ */ package org.apache.cassandra.auth; -import java.util.*; +import java.util.Collections; +import java.util.Map; +import java.util.Set; import org.apache.cassandra.exceptions.AuthenticationException; import org.apache.cassandra.exceptions.ConfigurationException; -import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.exceptions.RequestValidationException; /** * Provides a transitional IAuthenticator implementation for old-style (pre-1.2) authenticators. @@ -64,18 +67,17 @@ public abstract class LegacyAuthenticator implements IAuthenticator } @Override - public void create(String username, Map options) throws InvalidRequestException + public void create(String username, Map options) throws RequestValidationException, RequestExecutionException { } @Override - public void alter(String username, Map options) throws InvalidRequestException + public void alter(String username, Map options) throws RequestValidationException, RequestExecutionException { - throw new InvalidRequestException("ALTER USER operation is not supported by LegacyAuthenticator"); } @Override - public void drop(String username) throws InvalidRequestException + public void drop(String username) throws RequestValidationException, RequestExecutionException { } diff --git a/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java b/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java index 5948d34544..f834793c98 100644 --- a/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/LegacyAuthorizer.java @@ -72,14 +72,14 @@ public abstract class LegacyAuthorizer implements IAuthorizer @Override public void grant(AuthenticatedUser performer, Set permissions, IResource resource, String to) - throws InvalidRequestException, UnauthorizedException + throws InvalidRequestException { throw new InvalidRequestException("GRANT operation is not supported by LegacyAuthorizer"); } @Override public void revoke(AuthenticatedUser performer, Set permissions, IResource resource, String from) - throws InvalidRequestException, UnauthorizedException + throws InvalidRequestException { throw new InvalidRequestException("REVOKE operation is not supported by LegacyAuthorizer"); } diff --git a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java new file mode 100644 index 0000000000..f8f44d4f24 --- /dev/null +++ b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java @@ -0,0 +1,223 @@ +/* + * 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.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.cql3.UntypedResultSet; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.db.ConsistencyLevel; +import org.apache.cassandra.exceptions.AuthenticationException; +import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.exceptions.RequestExecutionException; +import org.apache.cassandra.service.StorageService; +import org.mindrot.jbcrypt.BCrypt; + +/** + * PasswordAuthenticator is an IAuthenticator implementation + * that keeps credentials (usernames and bcrypt-hashed passwords) + * internally in C* - in system_auth.credentials CQL3 table. + */ +public class PasswordAuthenticator implements IAuthenticator +{ + private static final Logger logger = LoggerFactory.getLogger(PasswordAuthenticator.class); + + private static final long DEFAULT_USER_SETUP_DELAY = 10; // seconds + + // 2 ** GENSALT_LOG2_ROUNS rounds of hashing will be performed. + private static final int GENSALT_LOG2_ROUNDS = 10; + + // name of the hash column. + private static final String SALTED_HASH = "salted_hash"; + + private static final String DEFAULT_USER_NAME = Auth.DEFAULT_SUPERUSER_NAME; + private static final String DEFAULT_USER_PASSWORD = Auth.DEFAULT_SUPERUSER_NAME; + + private static final String CREDENTIALS_CF = "credentials"; + private static final String CREDENTIALS_CF_SCHEMA = String.format("CREATE TABLE %s.%s (" + + "username text," + + "salted_hash text," // salt + hash + number of rounds + + "options map," // for future extensions + + "PRIMARY KEY(username)" + + ") WITH gc_grace_seconds=%d", + Auth.AUTH_KS, + CREDENTIALS_CF, + 90 * 24 * 60 * 60); // 3 months. + + // No anonymous access. + public boolean requireAuthentication() + { + return true; + } + + public Set