|
@@ -20,7 +20,6 @@ package org.apache.zookeeper.server.auth;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
import java.util.HashMap;
|
|
|
-import java.util.Iterator;
|
|
|
import java.util.Map;
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
@@ -36,125 +35,114 @@ import javax.security.sasl.AuthorizeCallback;
|
|
|
import javax.security.sasl.RealmCallback;
|
|
|
|
|
|
public class SaslServerCallbackHandler implements CallbackHandler {
|
|
|
- private String userName = null;
|
|
|
- private Map<String,String> credentials = new HashMap<String,String>();
|
|
|
- Logger LOG = LoggerFactory.getLogger(SaslServerCallbackHandler.class);
|
|
|
+ private static final String USER_PREFIX = "user_";
|
|
|
+ private static final Logger LOG = LoggerFactory.getLogger(SaslServerCallbackHandler.class);
|
|
|
+ private static final String SYSPROP_SUPER_PASSWORD = "zookeeper.SASLAuthenticationProvider.superPassword";
|
|
|
+ private static final String SYSPROP_REMOVE_HOST = "zookeeper.kerberos.removeHostFromPrincipal";
|
|
|
+ private static final String SYSPROP_REMOVE_REALM = "zookeeper.kerberos.removeRealmFromPrincipal";
|
|
|
+
|
|
|
+ private String userName;
|
|
|
+ private final Map<String,String> credentials = new HashMap<String,String>();
|
|
|
|
|
|
public SaslServerCallbackHandler(Configuration configuration) throws IOException {
|
|
|
AppConfigurationEntry configurationEntries[] = configuration.getAppConfigurationEntry("Server");
|
|
|
|
|
|
if (configurationEntries == null) {
|
|
|
- String errorMessage = "could not find a 'Server' entry in this configuration: server cannot start.";
|
|
|
+ String errorMessage = "Could not find a 'Server' entry in this configuration: Server cannot start.";
|
|
|
LOG.error(errorMessage);
|
|
|
- throw(new IOException(errorMessage));
|
|
|
+ throw new IOException(errorMessage);
|
|
|
}
|
|
|
credentials.clear();
|
|
|
for(AppConfigurationEntry entry: configurationEntries) {
|
|
|
Map<String,?> options = entry.getOptions();
|
|
|
// Populate DIGEST-MD5 user -> password map with JAAS configuration entries from the "Server" section.
|
|
|
// Usernames are distinguished from other options by prefixing the username with a "user_" prefix.
|
|
|
- Iterator it = options.entrySet().iterator();
|
|
|
- while (it.hasNext()) {
|
|
|
- Map.Entry pair = (Map.Entry)it.next();
|
|
|
- String key = (String)pair.getKey();
|
|
|
- if (key.substring(0,5).equals("user_")) {
|
|
|
- String userName = key.substring(5);
|
|
|
- credentials.put(userName,(String)pair.getValue());
|
|
|
- }
|
|
|
+ for(Map.Entry<String, ?> pair : options.entrySet()) {
|
|
|
+ String key = pair.getKey();
|
|
|
+ if (key.startsWith(USER_PREFIX)) {
|
|
|
+ String userName = key.substring(USER_PREFIX.length());
|
|
|
+ credentials.put(userName,(String)pair.getValue());
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
|
|
|
+ for (Callback callback : callbacks) {
|
|
|
+ if (callback instanceof NameCallback) {
|
|
|
+ handleNameCallback((NameCallback) callback);
|
|
|
+ } else if (callback instanceof PasswordCallback) {
|
|
|
+ handlePasswordCallback((PasswordCallback) callback);
|
|
|
+ } else if (callback instanceof RealmCallback) {
|
|
|
+ handleRealmCallback((RealmCallback) callback);
|
|
|
+ } else if (callback instanceof AuthorizeCallback) {
|
|
|
+ handleAuthorizeCallback((AuthorizeCallback) callback);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleNameCallback(NameCallback nc) {
|
|
|
+ // check to see if this user is in the user password database.
|
|
|
+ if (credentials.get(nc.getDefaultName()) == null) {
|
|
|
+ LOG.warn("User '" + nc.getDefaultName() + "' not found in list of DIGEST-MD5 authenticateable users.");
|
|
|
return;
|
|
|
}
|
|
|
+ nc.setName(nc.getDefaultName());
|
|
|
+ userName = nc.getDefaultName();
|
|
|
+ }
|
|
|
|
|
|
- public void handle(Callback[] callbacks) throws
|
|
|
- UnsupportedCallbackException {
|
|
|
- for (Callback callback : callbacks) {
|
|
|
- if (callback instanceof NameCallback) {
|
|
|
- NameCallback nc = (NameCallback) callback;
|
|
|
- // check to see if this user is in the user password database.
|
|
|
- if (credentials.get(nc.getDefaultName()) != null) {
|
|
|
- nc.setName(nc.getDefaultName());
|
|
|
- this.userName = nc.getDefaultName();
|
|
|
- }
|
|
|
- else { // no such user.
|
|
|
- LOG.warn("User '" + nc.getDefaultName() + "' not found in list of DIGEST-MD5 authenticateable users.");
|
|
|
- }
|
|
|
- }
|
|
|
- else {
|
|
|
- if (callback instanceof PasswordCallback) {
|
|
|
- PasswordCallback pc = (PasswordCallback) callback;
|
|
|
-
|
|
|
- if ((this.userName.equals("super")
|
|
|
- &&
|
|
|
- (System.getProperty("zookeeper.SASLAuthenticationProvider.superPassword") != null))) {
|
|
|
- // superuser: use Java system property for password, if available.
|
|
|
- pc.setPassword(System.getProperty("zookeeper.SASLAuthenticationProvider.superPassword").toCharArray());
|
|
|
- }
|
|
|
- else {
|
|
|
- if (this.credentials.get(this.userName) != null) {
|
|
|
- pc.setPassword(this.credentials.get(this.userName).toCharArray());
|
|
|
- }
|
|
|
- else {
|
|
|
- LOG.warn("No password found for user: " + this.userName);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- else {
|
|
|
- if (callback instanceof RealmCallback) {
|
|
|
- RealmCallback rc = (RealmCallback) callback;
|
|
|
- LOG.debug("client supplied realm: " + rc.getDefaultText());
|
|
|
- rc.setText(rc.getDefaultText());
|
|
|
- }
|
|
|
- else {
|
|
|
- if (callback instanceof AuthorizeCallback) {
|
|
|
- AuthorizeCallback ac = (AuthorizeCallback) callback;
|
|
|
-
|
|
|
- String authenticationID = ac.getAuthenticationID();
|
|
|
- String authorizationID = ac.getAuthorizationID();
|
|
|
-
|
|
|
- LOG.info("Successfully authenticated client: authenticationID=" + authenticationID + "; authorizationID=" + authorizationID + ".");
|
|
|
- if (authenticationID.equals(authorizationID)) {
|
|
|
- LOG.debug("setAuthorized(true) since " + authenticationID + "==" + authorizationID);
|
|
|
- ac.setAuthorized(true);
|
|
|
- } else {
|
|
|
- LOG.debug("setAuthorized(true), even though " + authenticationID + "!=" + authorizationID + ".");
|
|
|
- ac.setAuthorized(true);
|
|
|
- }
|
|
|
- if (ac.isAuthorized()) {
|
|
|
- LOG.debug("isAuthorized() since ac.isAuthorized() == true");
|
|
|
- // canonicalize authorization id according to system properties:
|
|
|
- // kerberos.removeRealmFromPrincipal(={true,false})
|
|
|
- // kerberos.removeHostFromPrincipal(={true,false})
|
|
|
- KerberosName kerberosName = new KerberosName(authenticationID);
|
|
|
- try {
|
|
|
- String userName = kerberosName.getShortName();
|
|
|
- if (!removeHost() && (kerberosName.getHostName() != null)) {
|
|
|
- userName += "/" + kerberosName.getServiceName();
|
|
|
- }
|
|
|
- if (!removeRealm() && (kerberosName.getRealm() != null)) {
|
|
|
- userName += "@" + kerberosName.getRealm();
|
|
|
- }
|
|
|
- LOG.info("Setting authorizedID: " + userName);
|
|
|
- ac.setAuthorizedID(userName);
|
|
|
- }
|
|
|
- catch (IOException e) {
|
|
|
- LOG.error("Failed to set name based on Kerberos authentication rules.");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ private void handlePasswordCallback(PasswordCallback pc) {
|
|
|
+ if ("super".equals(this.userName) && System.getProperty(SYSPROP_SUPER_PASSWORD) != null) {
|
|
|
+ // superuser: use Java system property for password, if available.
|
|
|
+ pc.setPassword(System.getProperty(SYSPROP_SUPER_PASSWORD).toCharArray());
|
|
|
+ } else if (credentials.containsKey(userName) ) {
|
|
|
+ pc.setPassword(credentials.get(userName).toCharArray());
|
|
|
+ } else {
|
|
|
+ LOG.warn("No password found for user: " + userName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleRealmCallback(RealmCallback rc) {
|
|
|
+ LOG.debug("client supplied realm: " + rc.getDefaultText());
|
|
|
+ rc.setText(rc.getDefaultText());
|
|
|
+ }
|
|
|
+
|
|
|
+ private void handleAuthorizeCallback(AuthorizeCallback ac) {
|
|
|
+ String authenticationID = ac.getAuthenticationID();
|
|
|
+ String authorizationID = ac.getAuthorizationID();
|
|
|
+
|
|
|
+ LOG.info("Successfully authenticated client: authenticationID=" + authenticationID
|
|
|
+ + "; authorizationID=" + authorizationID + ".");
|
|
|
+ ac.setAuthorized(true);
|
|
|
+
|
|
|
+ // canonicalize authorization id according to system properties:
|
|
|
+ // zookeeper.kerberos.removeRealmFromPrincipal(={true,false})
|
|
|
+ // zookeeper.kerberos.removeHostFromPrincipal(={true,false})
|
|
|
+ KerberosName kerberosName = new KerberosName(authenticationID);
|
|
|
+ try {
|
|
|
+ StringBuilder userNameBuilder = new StringBuilder(kerberosName.getShortName());
|
|
|
+ if (shouldAppendHost(kerberosName)) {
|
|
|
+ userNameBuilder.append("/").append(kerberosName.getHostName());
|
|
|
+ }
|
|
|
+ if (shouldAppendRealm(kerberosName)) {
|
|
|
+ userNameBuilder.append("@").append(kerberosName.getRealm());
|
|
|
}
|
|
|
+ LOG.info("Setting authorizedID: " + userNameBuilder);
|
|
|
+ ac.setAuthorizedID(userNameBuilder.toString());
|
|
|
+ } catch (IOException e) {
|
|
|
+ LOG.error("Failed to set name based on Kerberos authentication rules.");
|
|
|
}
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean shouldAppendRealm(KerberosName kerberosName) {
|
|
|
+ return !isSystemPropertyTrue(SYSPROP_REMOVE_REALM) && kerberosName.getRealm() != null;
|
|
|
+ }
|
|
|
|
|
|
- private boolean removeRealm() {
|
|
|
- return ((System.getProperty("zookeeper.kerberos.removeRealmFromPrincipal") != null) &&
|
|
|
- (System.getProperty("zookeeper.kerberos.removeRealmFromPrincipal").equals("true")));
|
|
|
+ private boolean shouldAppendHost(KerberosName kerberosName) {
|
|
|
+ return !isSystemPropertyTrue(SYSPROP_REMOVE_HOST) && kerberosName.getHostName() != null;
|
|
|
}
|
|
|
|
|
|
- private boolean removeHost() {
|
|
|
- return ((System.getProperty("zookeeper.kerberos.removeHostFromPrincipal") != null) &&
|
|
|
- (System.getProperty("zookeeper.kerberos.removeHostFromPrincipal").equals("true")));
|
|
|
+ private boolean isSystemPropertyTrue(String propertyName) {
|
|
|
+ return "true".equals(System.getProperty(propertyName));
|
|
|
}
|
|
|
}
|