PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java

https://github.com/beobal/cassandra
Java | 267 lines | 184 code | 37 blank | 46 comment | 24 complexity | 9f0001c08d6ea550b00658352e17dc3b MD5 | raw file
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.cassandra.auth;
  19. import java.net.InetAddress;
  20. import java.nio.charset.StandardCharsets;
  21. import java.util.Arrays;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import com.google.common.collect.ImmutableSet;
  25. import com.google.common.collect.Lists;
  26. import org.slf4j.Logger;
  27. import org.slf4j.LoggerFactory;
  28. import org.apache.cassandra.config.DatabaseDescriptor;
  29. import org.apache.cassandra.exceptions.RequestExecutionException;
  30. import org.apache.cassandra.schema.SchemaConstants;
  31. import org.apache.cassandra.cql3.QueryOptions;
  32. import org.apache.cassandra.cql3.QueryProcessor;
  33. import org.apache.cassandra.cql3.UntypedResultSet;
  34. import org.apache.cassandra.cql3.statements.SelectStatement;
  35. import org.apache.cassandra.exceptions.AuthenticationException;
  36. import org.apache.cassandra.exceptions.ConfigurationException;
  37. import org.apache.cassandra.service.ClientState;
  38. import org.apache.cassandra.service.QueryState;
  39. import org.apache.cassandra.transport.messages.ResultMessage;
  40. import org.apache.cassandra.utils.ByteBufferUtil;
  41. import org.mindrot.jbcrypt.BCrypt;
  42. import static org.apache.cassandra.auth.CassandraRoleManager.consistencyForRole;
  43. /**
  44. * PasswordAuthenticator is an IAuthenticator implementation
  45. * that keeps credentials (rolenames and bcrypt-hashed passwords)
  46. * internally in C* - in system_auth.roles CQL3 table.
  47. * Since 2.2, the management of roles (creation, modification,
  48. * querying etc is the responsibility of IRoleManager. Use of
  49. * PasswordAuthenticator requires the use of CassandraRoleManager
  50. * for storage and retrieval of encrypted passwords.
  51. */
  52. public class PasswordAuthenticator implements IAuthenticator
  53. {
  54. private static final Logger logger = LoggerFactory.getLogger(PasswordAuthenticator.class);
  55. // name of the hash column.
  56. private static final String SALTED_HASH = "salted_hash";
  57. // really this is a rolename now, but as it only matters for Thrift, we leave it for backwards compatibility
  58. public static final String USERNAME_KEY = "username";
  59. public static final String PASSWORD_KEY = "password";
  60. static final byte NUL = 0;
  61. private SelectStatement authenticateStatement;
  62. private CredentialsCache cache;
  63. // No anonymous access.
  64. public boolean requireAuthentication()
  65. {
  66. return true;
  67. }
  68. protected static boolean checkpw(String password, String hash)
  69. {
  70. try
  71. {
  72. return BCrypt.checkpw(password, hash);
  73. }
  74. catch (Exception e)
  75. {
  76. // Improperly formatted hashes may cause BCrypt.checkpw to throw, so trap any other exception as a failure
  77. logger.warn("Error: invalid password hash encountered, rejecting user", e);
  78. return false;
  79. }
  80. }
  81. private AuthenticatedUser authenticate(String username, String password) throws AuthenticationException
  82. {
  83. String hash = cache.get(username);
  84. if (!checkpw(password, hash))
  85. throw new AuthenticationException(String.format("Provided username %s and/or password are incorrect", username));
  86. return new AuthenticatedUser(username);
  87. }
  88. private String queryHashedPassword(String username)
  89. {
  90. try
  91. {
  92. ResultMessage.Rows rows =
  93. authenticateStatement.execute(QueryState.forInternalCalls(),
  94. QueryOptions.forInternalCalls(consistencyForRole(username),
  95. Lists.newArrayList(ByteBufferUtil.bytes(username))),
  96. System.nanoTime());
  97. // If either a non-existent role name was supplied, or no credentials
  98. // were found for that role we don't want to cache the result so we throw
  99. // an exception.
  100. if (rows.result.isEmpty())
  101. throw new AuthenticationException(String.format("Provided username %s and/or password are incorrect", username));
  102. UntypedResultSet result = UntypedResultSet.create(rows.result);
  103. if (!result.one().has(SALTED_HASH))
  104. throw new AuthenticationException(String.format("Provided username %s and/or password are incorrect", username));
  105. return result.one().getString(SALTED_HASH);
  106. }
  107. catch (RequestExecutionException e)
  108. {
  109. throw new AuthenticationException("Unable to perform authentication: " + e.getMessage(), e);
  110. }
  111. }
  112. public Set<DataResource> protectedResources()
  113. {
  114. // Also protected by CassandraRoleManager, but the duplication doesn't hurt and is more explicit
  115. return ImmutableSet.of(DataResource.table(SchemaConstants.AUTH_KEYSPACE_NAME, AuthKeyspace.ROLES));
  116. }
  117. public void validateConfiguration() throws ConfigurationException
  118. {
  119. }
  120. public void setup()
  121. {
  122. String query = String.format("SELECT %s FROM %s.%s WHERE role = ?",
  123. SALTED_HASH,
  124. SchemaConstants.AUTH_KEYSPACE_NAME,
  125. AuthKeyspace.ROLES);
  126. authenticateStatement = prepare(query);
  127. cache = new CredentialsCache(this);
  128. }
  129. public AuthenticatedUser legacyAuthenticate(Map<String, String> credentials) throws AuthenticationException
  130. {
  131. String username = credentials.get(USERNAME_KEY);
  132. if (username == null)
  133. throw new AuthenticationException(String.format("Required key '%s' is missing", USERNAME_KEY));
  134. String password = credentials.get(PASSWORD_KEY);
  135. if (password == null)
  136. throw new AuthenticationException(String.format("Required key '%s' is missing for provided username %s", PASSWORD_KEY, username));
  137. return authenticate(username, password);
  138. }
  139. public SaslNegotiator newSaslNegotiator(InetAddress clientAddress)
  140. {
  141. return new PlainTextSaslAuthenticator();
  142. }
  143. private static SelectStatement prepare(String query)
  144. {
  145. return (SelectStatement) QueryProcessor.getStatement(query, ClientState.forInternalCalls());
  146. }
  147. private class PlainTextSaslAuthenticator implements SaslNegotiator
  148. {
  149. private boolean complete = false;
  150. private String username;
  151. private String password;
  152. public byte[] evaluateResponse(byte[] clientResponse) throws AuthenticationException
  153. {
  154. decodeCredentials(clientResponse);
  155. complete = true;
  156. return null;
  157. }
  158. public boolean isComplete()
  159. {
  160. return complete;
  161. }
  162. public AuthenticatedUser getAuthenticatedUser() throws AuthenticationException
  163. {
  164. if (!complete)
  165. throw new AuthenticationException("SASL negotiation not complete");
  166. return authenticate(username, password);
  167. }
  168. /**
  169. * SASL PLAIN mechanism specifies that credentials are encoded in a
  170. * sequence of UTF-8 bytes, delimited by 0 (US-ASCII NUL).
  171. * The form is : {code}authzId<NUL>authnId<NUL>password<NUL>{code}
  172. * authzId is optional, and in fact we don't care about it here as we'll
  173. * set the authzId to match the authnId (that is, there is no concept of
  174. * a user being authorized to act on behalf of another with this IAuthenticator).
  175. *
  176. * @param bytes encoded credentials string sent by the client
  177. * @throws org.apache.cassandra.exceptions.AuthenticationException if either the
  178. * authnId or password is null
  179. */
  180. private void decodeCredentials(byte[] bytes) throws AuthenticationException
  181. {
  182. logger.trace("Decoding credentials from client token");
  183. byte[] user = null;
  184. byte[] pass = null;
  185. int end = bytes.length;
  186. for (int i = bytes.length - 1; i >= 0; i--)
  187. {
  188. if (bytes[i] == NUL)
  189. {
  190. if (pass == null)
  191. pass = Arrays.copyOfRange(bytes, i + 1, end);
  192. else if (user == null)
  193. user = Arrays.copyOfRange(bytes, i + 1, end);
  194. else
  195. throw new AuthenticationException("Credential format error: username or password is empty or contains NUL(\\0) character");
  196. end = i;
  197. }
  198. }
  199. if (pass == null || pass.length == 0)
  200. throw new AuthenticationException("Password must not be null");
  201. if (user == null || user.length == 0)
  202. throw new AuthenticationException("Authentication ID must not be null");
  203. username = new String(user, StandardCharsets.UTF_8);
  204. password = new String(pass, StandardCharsets.UTF_8);
  205. }
  206. }
  207. private static class CredentialsCache extends AuthCache<String, String> implements CredentialsCacheMBean
  208. {
  209. private CredentialsCache(PasswordAuthenticator authenticator)
  210. {
  211. super("CredentialsCache",
  212. DatabaseDescriptor::setCredentialsValidity,
  213. DatabaseDescriptor::getCredentialsValidity,
  214. DatabaseDescriptor::setCredentialsUpdateInterval,
  215. DatabaseDescriptor::getCredentialsUpdateInterval,
  216. DatabaseDescriptor::setCredentialsCacheMaxEntries,
  217. DatabaseDescriptor::getCredentialsCacheMaxEntries,
  218. authenticator::queryHashedPassword,
  219. () -> true);
  220. }
  221. public void invalidateCredentials(String roleName)
  222. {
  223. invalidate(roleName);
  224. }
  225. }
  226. public static interface CredentialsCacheMBean extends AuthCacheMBean
  227. {
  228. public void invalidateCredentials(String roleName);
  229. }
  230. }