You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by ee...@apache.org on 2010/01/19 03:10:43 UTC

svn commit: r900644 [2/2] - in /incubator/cassandra/trunk: conf/ interface/ interface/gen-java/org/apache/cassandra/service/ src/java/org/apache/cassandra/auth/ src/java/org/apache/cassandra/config/ src/java/org/apache/cassandra/service/

Added: incubator/cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java?rev=900644&view=auto
==============================================================================
--- incubator/cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java (added)
+++ incubator/cassandra/trunk/src/java/org/apache/cassandra/auth/SimpleAuthenticator.java Tue Jan 19 02:10:41 2010
@@ -0,0 +1,141 @@
+package org.apache.cassandra.auth;
+
+import java.io.*;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Properties;
+
+import org.apache.cassandra.service.*;
+
+public class SimpleAuthenticator implements IAuthenticator
+{
+    public final static String PASSWD_FILENAME_PROPERTY        = "passwd.properties";
+    public final static String AUTHORIZATION_FILENAME_PROPERTY = "authorization.properties";
+    public final static String PMODE_PROPERTY                  = "passwd.mode";
+    public static final String USERNAME_KEY                    = "username";
+    public static final String PASSWORD_KEY                    = "password";
+
+    public enum PasswordMode
+    {
+        PLAIN, MD5,
+    };
+
+    @Override
+    public void login(String keyspace, AuthenticationRequest authRequest) throws AuthenticationException, AuthorizationException
+    {
+        String pmode_plain = System.getProperty(PMODE_PROPERTY);
+        PasswordMode mode = PasswordMode.PLAIN;
+
+        if (null != pmode_plain)
+        {
+            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 = authRequest.getCredentials().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);
+        if (null == password) throw new AuthenticationException("Authentication request was missing the required key '" + PASSWORD_KEY + "'");
+
+        try
+        {
+            FileInputStream in = new FileInputStream(pfilename);
+            Properties props = new Properties();
+            props.load(in);
+            in.close();
+
+            // note we keep the message here and for the wrong password exactly the same to prevent attackers from guessing what users are valid
+            if (null == props.getProperty(username)) throw new AuthenticationException(authenticationErrorMessage(mode, username));
+            boolean authenticated = false;
+            switch (mode)
+            {
+                case PLAIN:
+                    authenticated = password.equals(props.getProperty(username));
+                    break;
+                case MD5:
+                    authenticated = MessageDigest.isEqual(password.getBytes(), MessageDigest.getInstance("MD5").digest(props.getProperty(username).getBytes()));
+                    break;
+            }
+
+            if (!authenticated) throw new AuthenticationException(authenticationErrorMessage(mode, username));
+        }
+        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());
+        }
+        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.getMessage());
+        }
+
+        // if we're here, the authentication succeeded. Now let's see if the user is authorized for this keyspace.
+
+        String afilename = System.getProperty(AUTHORIZATION_FILENAME_PROPERTY);
+        boolean authorized = false;
+        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 = true;
+            }
+
+            if (!authorized) throw new AuthorizationException(authorizationErrorMessage(keyspace, username));
+        }
+        catch (FileNotFoundException e)
+        {
+            throw new RuntimeException("Authorization table file given by property " + AUTHORIZATION_FILENAME_PROPERTY + " could not be found: " + e.getMessage());
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException("Authorization table file given by property " + AUTHORIZATION_FILENAME_PROPERTY + " could not be opened: " + e.getMessage());
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("Unexpected authorization problem: " + e.getMessage());
+        }
+    }
+
+    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);
+    }
+}

Modified: incubator/cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java?rev=900644&r1=900643&r2=900644&view=diff
==============================================================================
--- incubator/cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java (original)
+++ incubator/cassandra/trunk/src/java/org/apache/cassandra/config/DatabaseDescriptor.java Tue Jan 19 02:10:41 2010
@@ -18,6 +18,8 @@
 
 package org.apache.cassandra.config;
 
+import org.apache.cassandra.auth.AllowAllAuthenticator;
+import org.apache.cassandra.auth.IAuthenticator;
 import org.apache.cassandra.db.*;
 import org.apache.cassandra.db.marshal.AbstractType;
 import org.apache.cassandra.db.marshal.BytesType;
@@ -137,6 +139,8 @@
     private static boolean snapshotBeforeCompaction_;
     private static boolean autoBootstrap_ = false;
 
+    private static IAuthenticator authenticator = new AllowAllAuthenticator();
+
     static
     {
         try
@@ -221,6 +225,21 @@
                 indexAccessMode_ = diskAccessMode_;
             }
 
+            /* Authentication and authorization backend, implementing IAuthenticator */
+            String authenticatorClassName = xmlUtils.getNodeValue("/Storage/Authenticator");
+            if (authenticatorClassName != null)
+            {
+                try
+                {
+                    Class cls = Class.forName(authenticatorClassName);
+                    authenticator = (IAuthenticator) cls.getConstructor().newInstance();
+                }
+                catch (ClassNotFoundException e)
+                {
+                    throw new ConfigurationException("Invalid authenticator class " + authenticatorClassName);
+                }
+            }
+            
             /* Hashing strategy */
             String partitionerClassName = xmlUtils.getNodeValue("/Storage/Partitioner");
             if (partitionerClassName == null)
@@ -599,6 +618,11 @@
         }
     }
 
+    public static IAuthenticator getAuthenticator()
+    {
+        return authenticator;
+    }
+
     public static boolean isThriftFramed()
     {
         return thriftFramed_;

Modified: incubator/cassandra/trunk/src/java/org/apache/cassandra/service/CassandraServer.java
URL: http://svn.apache.org/viewvc/incubator/cassandra/trunk/src/java/org/apache/cassandra/service/CassandraServer.java?rev=900644&r1=900643&r2=900644&view=diff
==============================================================================
--- incubator/cassandra/trunk/src/java/org/apache/cassandra/service/CassandraServer.java (original)
+++ incubator/cassandra/trunk/src/java/org/apache/cassandra/service/CassandraServer.java Tue Jan 19 02:10:41 2010
@@ -28,6 +28,7 @@
 
 import org.apache.commons.lang.ArrayUtils;
 
+import org.apache.cassandra.auth.*;
 import org.apache.cassandra.config.CFMetaData;
 import org.apache.cassandra.config.DatabaseDescriptor;
 import org.apache.cassandra.db.*;
@@ -49,6 +50,16 @@
     private final static List<ColumnOrSuperColumn> EMPTY_COLUMNS = Collections.emptyList();
     private final static List<Column> EMPTY_SUBCOLUMNS = Collections.emptyList();
 
+    // will be set only by login()
+    private ThreadLocal<Boolean> loginDone = new ThreadLocal<Boolean>() 
+    {
+        @Override
+        protected Boolean initialValue()
+        {
+            return false;
+        }
+    };
+
     /*
       * Handle to the storage service to interact with the other machines in the
       * cluster.
@@ -59,7 +70,7 @@
     {
         storageService = StorageService.instance;
     }
-
+    
     protected Map<String, ColumnFamily> readColumnFamily(List<ReadCommand> commands, ConsistencyLevel consistency_level)
     throws InvalidRequestException, UnavailableException, TimedOutException
     {
@@ -198,6 +209,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("get_slice");
+
+        checkLoginDone();
+
         return multigetSliceInternal(keyspace, Arrays.asList(key), column_parent, predicate, consistency_level).get(key);
     }
     
@@ -206,6 +220,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("multiget_slice");
+
+        checkLoginDone();
+
         return multigetSliceInternal(keyspace, keys, column_parent, predicate, consistency_level);
     }
 
@@ -242,6 +259,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("get");
+
+        checkLoginDone();
+
         ColumnOrSuperColumn column = multigetInternal(table, Arrays.asList(key), column_path, consistency_level).get(key);
         if (!column.isSetColumn() && !column.isSetSuper_column())
         {
@@ -291,6 +311,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("multiget");
+
+        checkLoginDone();
+
         return multigetInternal(table, keys, column_path, consistency_level);
     }
 
@@ -349,6 +372,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("get_count");
+
+        checkLoginDone();
+
         return multigetCountInternal(table, Arrays.asList(key), column_parent, consistency_level).get(key);
     }
 
@@ -394,6 +420,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("insert");
+
+        checkLoginDone();
+
         ThriftValidation.validateKey(key);
         ThriftValidation.validateColumnPath(table, column_path);
 
@@ -414,6 +443,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("batch_insert");
+
+        checkLoginDone();
+
         ThriftValidation.validateKey(key);
 
         for (String cfName : cfmap.keySet())
@@ -432,7 +464,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("batch_mutate");
-        
+
+        checkLoginDone();
+
         List<RowMutation> rowMutations = new ArrayList<RowMutation>();
         for (Map.Entry<String, Map<String, List<Mutation>>> mutationEntry: mutation_map.entrySet())
         {
@@ -473,6 +507,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("remove");
+
+        checkLoginDone();
+
         ThriftValidation.validateKey(key);
         ThriftValidation.validateColumnPathOrParent(table, column_path);
         
@@ -586,6 +623,8 @@
         if (logger.isDebugEnabled())
             logger.debug("range_slice");
 
+        checkLoginDone();
+
         ThriftValidation.validatePredicate(keyspace, column_parent, predicate);
         if (!StorageService.getPartitioner().preservesOrder())
         {
@@ -629,6 +668,9 @@
     {
         if (logger.isDebugEnabled())
             logger.debug("get_key_range");
+
+        checkLoginDone();
+
         ThriftValidation.validateCommand(tablename, columnFamily);
         if (!StorageService.getPartitioner().preservesOrder())
         {
@@ -653,5 +695,18 @@
         }
     }
 
+    @Override
+    public void login(String keyspace, AuthenticationRequest auth_request) throws AuthenticationException, AuthorizationException, TException
+    {
+        DatabaseDescriptor.getAuthenticator().login(keyspace, auth_request);
+        loginDone.set(true);
+    }
+
+    protected void checkLoginDone() throws InvalidRequestException
+    {
+        if (!loginDone.get()) throw new InvalidRequestException("Login is required before any other API calls");
+    }
+
+    
     // main method moved to CassandraDaemon
 }