mirror of https://github.com/apache/cassandra
new IAuthenticator interface. patch by stuhood, reviewed by gdusbabek. CASSANDRA-1237
git-svn-id: https://svn.apache.org/repos/asf/cassandra/trunk@980222 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
7b9f69ad18
commit
3f9f50725b
|
|
@ -1,22 +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 SimpleAuthenticator. The format of
|
||||
# this file is keyspace=users, where users is a comma delimited list of
|
||||
# authenticatable users from passwd.properties. This file contains
|
||||
# potentially sensitive information, keep this in mind when setting its
|
||||
# mode and ownership.
|
||||
Keyspace1=jsmith,Elvis Presley,dilbert
|
||||
|
|
@ -20,25 +20,31 @@ package org.apache.cassandra.auth;
|
|||
*
|
||||
*/
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.config.ConfigurationException;
|
||||
import org.apache.cassandra.thrift.AccessLevel;
|
||||
import org.apache.cassandra.thrift.AuthenticationException;
|
||||
import org.apache.cassandra.thrift.AuthenticationRequest;
|
||||
import org.apache.cassandra.thrift.AuthorizationException;
|
||||
|
||||
public class AllowAllAuthenticator implements IAuthenticator
|
||||
{
|
||||
private final static AuthenticatedUser USER = new AuthenticatedUser("allow_all", true);
|
||||
|
||||
@Override
|
||||
public AccessLevel login(String keyspace, AuthenticationRequest authRequest) throws AuthenticationException, AuthorizationException
|
||||
public AuthenticatedUser defaultUser()
|
||||
{
|
||||
// do nothing, allow anything
|
||||
return AccessLevel.FULL;
|
||||
return USER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedUser login(Map<String,String> credentials) throws AuthenticationException, AuthorizationException
|
||||
{
|
||||
return USER;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void validateConfiguration() throws ConfigurationException
|
||||
{
|
||||
{
|
||||
// do nothing, no configuration to validate
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
*
|
||||
* 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.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.avro.AccessLevel;
|
||||
|
||||
/**
|
||||
* An authenticated user and her groups.
|
||||
*/
|
||||
public class AuthenticatedUser
|
||||
{
|
||||
public final String username;
|
||||
public final Set<String> groups;
|
||||
public final boolean isSuper;
|
||||
|
||||
public AuthenticatedUser(String username, boolean isSuper)
|
||||
{
|
||||
this.username = username;
|
||||
this.groups = Collections.emptySet();
|
||||
this.isSuper = isSuper;
|
||||
}
|
||||
|
||||
public AuthenticatedUser(String username, Set<String> groups, boolean isSuper)
|
||||
{
|
||||
this.username = username;
|
||||
this.groups = Collections.unmodifiableSet(groups);
|
||||
this.isSuper = isSuper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The access level granted to the user by the given access maps.
|
||||
*/
|
||||
public AccessLevel levelFor(Map<String,AccessLevel> usersAccess, Map<String,AccessLevel> groupsAccess)
|
||||
{
|
||||
// determine the maximum access level for this user and groups
|
||||
AccessLevel level = usersAccess.get(username);
|
||||
if (level == null)
|
||||
level = AccessLevel.NONE;
|
||||
for (String group : groups)
|
||||
{
|
||||
AccessLevel forGroup = groupsAccess.get(group);
|
||||
if (forGroup != null && forGroup.ordinal() > level.ordinal())
|
||||
level = forGroup;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "#<User %s groups=%s>".format(username, groups);
|
||||
}
|
||||
}
|
||||
|
|
@ -20,16 +20,20 @@ package org.apache.cassandra.auth;
|
|||
*
|
||||
*/
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.config.ConfigurationException;
|
||||
import org.apache.cassandra.thrift.AccessLevel;
|
||||
import org.apache.cassandra.thrift.AuthenticationException;
|
||||
import org.apache.cassandra.thrift.AuthenticationRequest;
|
||||
import org.apache.cassandra.thrift.AuthorizationException;
|
||||
|
||||
public interface IAuthenticator
|
||||
{
|
||||
public AccessLevel login(String keyspace, AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException;
|
||||
/**
|
||||
* @return The user that a connection is initialized with, or 'null' if a user must call login().
|
||||
*/
|
||||
public AuthenticatedUser defaultUser();
|
||||
|
||||
public AuthenticatedUser login(Map<String,String> credentials) throws AuthenticationException, AuthorizationException;
|
||||
|
||||
public void validateConfiguration() throws ConfigurationException;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,17 +25,15 @@ import java.io.*;
|
|||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Properties;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.config.ConfigurationException;
|
||||
import org.apache.cassandra.thrift.AccessLevel;
|
||||
import org.apache.cassandra.thrift.AuthenticationException;
|
||||
import org.apache.cassandra.thrift.AuthenticationRequest;
|
||||
import org.apache.cassandra.thrift.AuthorizationException;
|
||||
|
||||
public class SimpleAuthenticator implements IAuthenticator
|
||||
{
|
||||
public final static String PASSWD_FILENAME_PROPERTY = "passwd.properties";
|
||||
public final static String ACCESS_FILENAME_PROPERTY = "access.properties";
|
||||
public final static String PMODE_PROPERTY = "passwd.mode";
|
||||
public static final String USERNAME_KEY = "username";
|
||||
public static final String PASSWORD_KEY = "password";
|
||||
|
|
@ -46,7 +44,14 @@ public class SimpleAuthenticator implements IAuthenticator
|
|||
};
|
||||
|
||||
@Override
|
||||
public AccessLevel login(String keyspace, AuthenticationRequest authRequest) throws AuthenticationException, AuthorizationException
|
||||
public AuthenticatedUser defaultUser()
|
||||
{
|
||||
// users must log in
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedUser login(Map<String,String> credentials) throws AuthenticationException, AuthorizationException
|
||||
{
|
||||
String pmode_plain = System.getProperty(PMODE_PROPERTY);
|
||||
PasswordMode mode = PasswordMode.PLAIN;
|
||||
|
|
@ -71,10 +76,10 @@ public class SimpleAuthenticator implements IAuthenticator
|
|||
|
||||
String pfilename = System.getProperty(PASSWD_FILENAME_PROPERTY);
|
||||
|
||||
String username = authRequest.getCredentials().get(USERNAME_KEY);
|
||||
String username = credentials.get(USERNAME_KEY);
|
||||
if (null == username) throw new AuthenticationException("Authentication request was missing the required key '" + USERNAME_KEY + "'");
|
||||
|
||||
String password = authRequest.getCredentials().get(PASSWORD_KEY);
|
||||
String password = credentials.get(PASSWORD_KEY);
|
||||
if (null == password) throw new AuthenticationException("Authentication request was missing the required key '" + PASSWORD_KEY + "'");
|
||||
|
||||
boolean authenticated = false;
|
||||
|
|
@ -96,15 +101,13 @@ public class SimpleAuthenticator implements IAuthenticator
|
|||
case MD5:
|
||||
authenticated = MessageDigest.isEqual(password.getBytes(), MessageDigest.getInstance("MD5").digest(props.getProperty(username).getBytes()));
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unknown PasswordMode " + mode);
|
||||
}
|
||||
}
|
||||
catch (NoSuchAlgorithmException e)
|
||||
{
|
||||
throw new AuthenticationException("You requested MD5 checking but the MD5 digest algorithm is not available: " + e.getMessage());
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new RuntimeException("Authentication table file given by property " + PASSWD_FILENAME_PROPERTY + " could not be found: " + e.getMessage());
|
||||
throw new RuntimeException("You requested MD5 checking but the MD5 digest algorithm is not available: " + e.getMessage());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
|
@ -117,63 +120,21 @@ public class SimpleAuthenticator implements IAuthenticator
|
|||
|
||||
if (!authenticated) throw new AuthenticationException(authenticationErrorMessage(mode, username));
|
||||
|
||||
// if we're here, the authentication succeeded. Now let's see if the user is authorized for this keyspace.
|
||||
|
||||
String afilename = System.getProperty(ACCESS_FILENAME_PROPERTY);
|
||||
AccessLevel authorized = AccessLevel.NONE;
|
||||
try
|
||||
{
|
||||
FileInputStream in = new FileInputStream(afilename);
|
||||
Properties props = new Properties();
|
||||
props.load(in);
|
||||
in.close();
|
||||
|
||||
// structure:
|
||||
// given keyspace X, users A B and C can be authorized like this (separate their names with spaces):
|
||||
// X = A B C
|
||||
|
||||
// note we keep the message here and for other authorization problems exactly the same to prevent attackers
|
||||
// from guessing what keyspaces are valid
|
||||
if (null == props.getProperty(keyspace))
|
||||
throw new AuthorizationException(authorizationErrorMessage(keyspace, username));
|
||||
|
||||
for (String allow : props.getProperty(keyspace).split(","))
|
||||
{
|
||||
if (allow.equals(username)) authorized = AccessLevel.FULL;
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new RuntimeException("Authorization table file given by property " + ACCESS_FILENAME_PROPERTY + " could not be found: " + e.getMessage());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException("Authorization table file given by property " + ACCESS_FILENAME_PROPERTY + " could not be opened: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (authorized == AccessLevel.NONE) throw new AuthorizationException(authorizationErrorMessage(keyspace, username));
|
||||
|
||||
return authorized;
|
||||
// TODO: Should allow/require a user to configure a 'super' username.
|
||||
return new AuthenticatedUser(username, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public void validateConfiguration() throws ConfigurationException
|
||||
{
|
||||
String aFileName = System.getProperty(SimpleAuthenticator.ACCESS_FILENAME_PROPERTY);
|
||||
String pfilename = System.getProperty(SimpleAuthenticator.PASSWD_FILENAME_PROPERTY);
|
||||
if (aFileName == null || pfilename == null)
|
||||
if (pfilename == null)
|
||||
{
|
||||
throw new ConfigurationException("When using " + this.getClass().getCanonicalName() + " " +
|
||||
SimpleAuthenticator.ACCESS_FILENAME_PROPERTY + " and " +
|
||||
SimpleAuthenticator.PASSWD_FILENAME_PROPERTY + " properties must be defined.");
|
||||
}
|
||||
}
|
||||
|
||||
static String authorizationErrorMessage(String keyspace, String username)
|
||||
{
|
||||
return String.format("User %s could not be authorized to use keyspace %s", username, keyspace);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.auth.AllowAllAuthenticator;
|
||||
import org.apache.cassandra.auth.AuthenticatedUser;
|
||||
import org.apache.cassandra.concurrent.StageManager;
|
||||
import org.apache.cassandra.config.ConfigurationException;
|
||||
import org.apache.cassandra.config.KSMetaData;
|
||||
|
|
@ -66,14 +67,14 @@ public class CassandraServer implements Cassandra.Iface
|
|||
private final static List<Column> EMPTY_SUBCOLUMNS = Collections.emptyList();
|
||||
|
||||
// will be set only by login()
|
||||
private ThreadLocal<AccessLevel> loginDone = new ThreadLocal<AccessLevel>()
|
||||
{
|
||||
private ThreadLocal<AuthenticatedUser> loginDone = new ThreadLocal<AuthenticatedUser>() {
|
||||
@Override
|
||||
protected AccessLevel initialValue()
|
||||
public AuthenticatedUser initialValue()
|
||||
{
|
||||
return AccessLevel.NONE;
|
||||
return DatabaseDescriptor.getAuthenticator().defaultUser();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Keyspace associated with session
|
||||
*/
|
||||
|
|
@ -706,22 +707,14 @@ public class CassandraServer implements Cassandra.Iface
|
|||
return splits;
|
||||
}
|
||||
|
||||
public AccessLevel login(AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException, TException
|
||||
public void login(AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException, TException
|
||||
{
|
||||
AccessLevel level;
|
||||
|
||||
if (keySpace.get() == null)
|
||||
{
|
||||
throw new AuthenticationException("You have not set a specific keyspace; please call set_keyspace first");
|
||||
}
|
||||
|
||||
level = DatabaseDescriptor.getAuthenticator().login(keySpace.get(), auth_request);
|
||||
AuthenticatedUser user = DatabaseDescriptor.getAuthenticator().login(auth_request.getCredentials());
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("login confirmed; new access level is " + level);
|
||||
logger.debug("login confirmed; user is " + user);
|
||||
|
||||
loginDone.set(level);
|
||||
return level;
|
||||
loginDone.set(user);
|
||||
}
|
||||
|
||||
public void logout()
|
||||
|
|
@ -735,18 +728,11 @@ public class CassandraServer implements Cassandra.Iface
|
|||
|
||||
protected void checkKeyspaceAndLoginAuthorized(AccessLevel level) throws InvalidRequestException
|
||||
{
|
||||
if (keySpace.get() == null)
|
||||
{
|
||||
throw new InvalidRequestException("You have not assigned a keyspace; please use set_keyspace (and login if necessary)");
|
||||
}
|
||||
|
||||
if (!(DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator))
|
||||
{
|
||||
if (loginDone.get() == null)
|
||||
throw new InvalidRequestException("You have not logged into keyspace " + keySpace.get());
|
||||
if (loginDone.get().getValue() < level.getValue())
|
||||
throw new InvalidRequestException("Your credentials are not sufficient to perform " + level + " operations");
|
||||
}
|
||||
if (loginDone.get() == null)
|
||||
throw new InvalidRequestException("You have not logged in");
|
||||
|
||||
// FIXME: if no keyspace set, check against global authlist. otherwise, check
|
||||
// against keyspace authlist
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -877,6 +863,7 @@ public class CassandraServer implements Cassandra.Iface
|
|||
// IAuthenticator was devised prior to, and without thought for, dynamic keyspace creation. As
|
||||
// a result, we must choose between letting anyone/everyone create keyspaces (which they likely
|
||||
// won't even be able to use), or be honest and disallow it entirely if configured for auth.
|
||||
// See CASSANDRA-1271 for a proposed solution.
|
||||
if (!(DatabaseDescriptor.getAuthenticator() instanceof AllowAllAuthenticator))
|
||||
throw new InvalidRequestException("Unable to create new keyspace while authentication is enabled.");
|
||||
|
||||
|
|
@ -1034,10 +1021,6 @@ public class CassandraServer implements Cassandra.Iface
|
|||
throw new InvalidRequestException("Keyspace does not exist");
|
||||
}
|
||||
|
||||
// If switching, invalidate previous access level; force a new login.
|
||||
if (keySpace.get() != null && !keySpace.get().equals(keyspace))
|
||||
loginDone.set(AccessLevel.NONE);
|
||||
|
||||
keySpace.set(keyspace);
|
||||
requestSchedulerId.set(keyspace);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue