Skip to content

Instantly share code, notes, and snippets.

@ekoontz
Created January 26, 2011 02:35
Show Gist options
  • Save ekoontz/796130 to your computer and use it in GitHub Desktop.
Save ekoontz/796130 to your computer and use it in GitHub Desktop.
SASL patch: works with arbitrary Kerberos client and service principals provided properly-configured zoo.cfg, JAAS configuration file, and associated keytab(s) are available.
diff --git a/src/java/main/org/apache/zookeeper/ClientCnxn.java b/src/java/main/org/apache/zookeeper/ClientCnxn.java
index 8e449db..854afc6 100644
--- a/src/java/main/org/apache/zookeeper/ClientCnxn.java
+++ b/src/java/main/org/apache/zookeeper/ClientCnxn.java
@@ -24,11 +24,20 @@ import java.lang.Thread.UncaughtExceptionHandler;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
+import java.security.PrivilegedExceptionAction;
import java.util.LinkedList;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
import org.apache.jute.BinaryInputArchive;
import org.apache.jute.BinaryOutputArchive;
@@ -48,20 +57,8 @@ import org.apache.zookeeper.ZooDefs.OpCode;
import org.apache.zookeeper.ZooKeeper.States;
import org.apache.zookeeper.ZooKeeper.WatchRegistration;
import org.apache.zookeeper.client.HostProvider;
-import org.apache.zookeeper.proto.AuthPacket;
-import org.apache.zookeeper.proto.ConnectRequest;
-import org.apache.zookeeper.proto.CreateResponse;
-import org.apache.zookeeper.proto.ExistsResponse;
-import org.apache.zookeeper.proto.GetACLResponse;
-import org.apache.zookeeper.proto.GetChildren2Response;
-import org.apache.zookeeper.proto.GetChildrenResponse;
-import org.apache.zookeeper.proto.GetDataResponse;
-import org.apache.zookeeper.proto.ReplyHeader;
-import org.apache.zookeeper.proto.RequestHeader;
-import org.apache.zookeeper.proto.SetACLResponse;
-import org.apache.zookeeper.proto.SetDataResponse;
-import org.apache.zookeeper.proto.SetWatches;
-import org.apache.zookeeper.proto.WatcherEvent;
+import org.apache.zookeeper.data.Stat;
+import org.apache.zookeeper.proto.*;
import org.apache.zookeeper.server.ByteBufferInputStream;
import org.apache.zookeeper.server.ZooTrace;
@@ -154,6 +151,78 @@ public class ClientCnxn {
*/
private final HostProvider hostProvider;
+ private Subject subject;
+ private SaslClient saslClient;
+ private byte[] saslToken = new byte[0];
+
+ public void prepareSaslResponseToServer(byte[] serverToken) {
+ saslToken = serverToken;
+
+ LOG.info("saslToken (server) length: " + saslToken.length);
+
+ if (saslClient.isComplete() == true) {
+ LOG.debug("*****ClientCnxn:run(): SASL negotiation COMPLETE*****");
+ state = States.CONNECTED;
+ }
+ else {
+ saslToken = createSaslToken(saslToken);
+ LOG.info("saslToken (client) length: " + saslToken.length);
+ queueSaslPacket(saslToken);
+ state = States.SASL;
+ }
+ }
+
+ byte[] createSaslToken(final byte[] saslToken) {
+ if (saslToken == null) {
+ // TODO: introspect about runtime environment (such as jaas.conf)
+ LOG.error("Experienced a fatal error in authenticating with a Zookeeper Quorum member: the quorum member's saslToken is null:");
+ System.exit(-1);
+ }
+ try {
+ final byte[] retval =
+ Subject.doAs(subject, new PrivilegedExceptionAction<byte[]>() {
+ public byte[] run() {
+ try {
+ LOG.debug("ClientCnxn:createSaslToken(): ->saslClient.evaluateChallenge(len="+saslToken.length+")");
+ return saslClient.evaluateChallenge(saslToken);
+ }
+ catch (NullPointerException e) {
+ LOG.error("Quorum Member's SASL challenge was null.");
+ }
+ catch (SaslException e) {
+ LOG.error("Quorum Member's SASL challenge caused a SASLException:",e);
+ e.printStackTrace();
+ }
+ // returning null here will result in client going to AUTH_FAILED.
+ return null;
+ }
+ });
+ LOG.debug("Successfully created initial token with length:"+retval.length);
+ return retval;
+ }
+ catch (Exception e) {
+ // TODO: introspect about runtime environment (such as jaas.conf)
+ // to give hints to user.
+ LOG.error("Some kind of error occurred when evaluating Zookeeper Quorum Member's received SASL token. Client will go to AUTH_FAILED state.");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private void queueSaslPacket(byte[] saslToken) {
+ LOG.debug("ClientCnxn:sendSaslPacket:length="+saslToken.length);
+ RequestHeader h = new RequestHeader();
+ h.setType(ZooDefs.OpCode.sasl);
+ GetSASLRequest request = new GetSASLRequest();
+ request.setToken(saslToken);
+ SetSASLResponse response = new SetSASLResponse();
+
+ ServerSaslResponseCallback cb = new ServerSaslResponseCallback();
+
+ ReplyHeader r = new ReplyHeader();
+ Packet packet = queuePacket(h, r, request, response, cb, null, null, this, null);
+ }
+
public long getSessionId() {
return sessionId;
}
@@ -280,9 +349,9 @@ public class ClientCnxn {
* @throws IOException
*/
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
- ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket)
+ ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket, Subject subject, SaslClient saslClient)
throws IOException {
- this(chrootPath, hostProvider, sessionTimeout, zooKeeper, watcher, clientCnxnSocket, 0, new byte[16]);
+ this(chrootPath, hostProvider, sessionTimeout, zooKeeper, watcher, clientCnxnSocket, 0, new byte[16], subject, saslClient);
}
/**
@@ -306,7 +375,7 @@ public class ClientCnxn {
*/
public ClientCnxn(String chrootPath, HostProvider hostProvider, int sessionTimeout, ZooKeeper zooKeeper,
ClientWatchManager watcher, ClientCnxnSocket clientCnxnSocket,
- long sessionId, byte[] sessionPasswd) {
+ long sessionId, byte[] sessionPasswd, Subject subject, SaslClient saslClient) {
this.zooKeeper = zooKeeper;
this.watcher = watcher;
this.sessionId = sessionId;
@@ -314,12 +383,13 @@ public class ClientCnxn {
this.sessionTimeout = sessionTimeout;
this.hostProvider = hostProvider;
this.chrootPath = chrootPath;
+ this.saslClient = saslClient;
connectTimeout = sessionTimeout / hostProvider.size();
readTimeout = sessionTimeout * 2 / 3;
-
- sendThread = new SendThread(clientCnxnSocket);
+ sendThread = new SendThread(clientCnxnSocket,this);
eventThread = new EventThread();
+ this.subject = subject;
}
/**
@@ -489,6 +559,11 @@ public class ClientCnxn {
} else {
cb.processResult(rc, clientPath, p.ctx, null);
}
+ } else if (p.cb instanceof ServerSaslResponseCallback) {
+ ServerSaslResponseCallback cb = (ServerSaslResponseCallback) p.cb;
+ SetSASLResponse rsp = (SetSASLResponse) p.response;
+ // TODO : check rc (== 0, etc) as with other packet types.
+ cb.processResult(rc,null,p.ctx,rsp.getToken(),null);
} else if (p.response instanceof GetDataResponse) {
DataCallback cb = (DataCallback) p.cb;
GetDataResponse rsp = (GetDataResponse) p.response;
@@ -543,6 +618,7 @@ public class ClientCnxn {
VoidCallback cb = (VoidCallback) p.cb;
cb.processResult(rc, clientPath, p.ctx);
}
+
}
} catch (Throwable t) {
LOG.error("Caught unexpected throwable", t);
@@ -577,6 +653,7 @@ public class ClientCnxn {
case CLOSED:
p.replyHeader.setErr(KeeperException.Code.SESSIONEXPIRED.intValue());
break;
+ // TODO: handle state.SASL_* states (if necessary)
default:
p.replyHeader.setErr(KeeperException.Code.CONNECTIONLOSS.intValue());
}
@@ -617,6 +694,25 @@ public class ClientCnxn {
public static final int packetLen = Integer.getInteger("jute.maxbuffer",
4096 * 1024);
+
+ class ServerSaslResponseCallback implements DataCallback {
+ public void processResult(int rc, String path, Object ctx, byte data[], Stat stat) {
+ // data[] contains the Zookeeper Server's SASL token.
+ // ctx is the ClientCnxn object. We use this object's prepareSaslResponseToServer() method
+ // to reply to the Zookeeper Server's SASL token
+ ClientCnxn cnxn = (ClientCnxn)ctx;
+ byte[] usedata = data;
+ if (data != null) {
+ LOG.debug("ServerSaslResponseCallback(): saslToken server response: (length="+usedata.length+")");
+ }
+ else {
+ usedata = new byte[0];
+ LOG.debug("ServerSaslResponseCallback(): using empty data[] as server response (length="+usedata.length+")");
+ }
+ cnxn.prepareSaslResponseToServer(usedata);
+ }
+ }
+
/**
* This class services the outgoing request queue and generates the heart
* beats. It also spawns the ReadThread.
@@ -686,6 +782,7 @@ public class ClientCnxn {
return;
}
Packet packet;
+
synchronized (pendingQueue) {
if (pendingQueue.size() == 0) {
throw new IOException("Nothing in the queue, but got "
@@ -693,6 +790,7 @@ public class ClientCnxn {
}
packet = pendingQueue.remove();
}
+
/*
* Since requests are processed in order, we better get a response
* to the first request!
@@ -729,10 +827,13 @@ public class ClientCnxn {
}
}
- SendThread(ClientCnxnSocket clientCnxnSocket) {
+ final ClientCnxn cnxn;
+
+ SendThread(ClientCnxnSocket clientCnxnSocket, final ClientCnxn cnxn) {
super(makeThreadName("-SendThread()"));
state = States.CONNECTING;
this.clientCnxnSocket = clientCnxnSocket;
+ this.cnxn = cnxn;
setUncaughtExceptionHandler(uncaughtExceptionHandler);
setDaemon(true);
}
@@ -839,7 +940,41 @@ public class ClientCnxn {
startConnect();
clientCnxnSocket.updateLastSendAndHeard();
}
-
+
+ if (state == States.SASL_INITIAL) {
+ if (saslClient.isComplete() == true) {
+ // TODO: this happens when client re-connects after authenticating:
+ // should re-authenticate in this case rather than going straight to CONNECTED.
+ state = States.CONNECTED;
+ LOG.warn("Unexpectedly, SASL negotiation is complete while client is in SASL_INITIAL state. Going to CONNECTED with no intervening SASL negotiation with Zookeeper Quorum member.");
+ }
+ else {
+ if (saslClient.hasInitialResponse() == true) {
+ LOG.info("saslClient.hasInitialResponse()==true");
+ LOG.info("hasInitialResponse() == true; (1) SASL token length = " + cnxn.saslToken.length);
+ cnxn.saslToken = createSaslToken(cnxn.saslToken);
+ LOG.info("hasInitialResponse() == true; (2) SASL token length = " + cnxn.saslToken.length);
+ if (cnxn.saslToken == null) {
+ state = States.AUTH_FAILED;
+ LOG.debug("SASL negotiation with Zookeeper Quorum member failed: client state is now AUTH_FAILED.");
+ }
+ else {
+ queueSaslPacket(cnxn.saslToken);
+ state = States.SASL;
+ }
+ }
+ else {
+ LOG.info("saslClient.hasInitialResponse()==false");
+ }
+ }
+ }
+ if (state == States.SASL) {
+ if (saslClient.isComplete() == true) {
+ LOG.debug("SASL negotiation COMPLETE*****! SASL->CONNECTED.");
+ state = States.CONNECTED;
+ }
+ }
+
if (state == States.CONNECTED) {
to = readTimeout - clientCnxnSocket.getIdleRecv();
} else {
@@ -866,7 +1001,6 @@ public class ClientCnxn {
}
}
}
-
clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue);
} catch (Exception e) {
@@ -961,7 +1095,14 @@ public class ClientCnxn {
hostProvider.onConnected();
sessionId = _sessionId;
sessionPasswd = _sessionPasswd;
- state = States.CONNECTED;
+ // Big Red SASL on-off switch: true -> SASL is on; false otherwise.
+ if (true) {
+ state = States.SASL_INITIAL;
+
+ }
+ else {
+ state = States.CONNECTED;
+ }
LOG.info("Session establishment complete on server "
+ clientCnxnSocket.getRemoteSocketAddress() + ", sessionid = 0x"
+ Long.toHexString(sessionId) + ", negotiated timeout = "
@@ -1047,7 +1188,7 @@ public class ClientCnxn {
Record response, AsyncCallback cb, String clientPath,
String serverPath, Object ctx, WatchRegistration watchRegistration)
{
- Packet packet = null;
+ Packet packet;
synchronized (outgoingQueue) {
if (h.getType() != OpCode.ping && h.getType() != OpCode.auth) {
h.setXid(getXid());
@@ -1085,4 +1226,6 @@ public class ClientCnxn {
States getState() {
return state;
}
+
+
}
diff --git a/src/java/main/org/apache/zookeeper/ClientCnxnSocket.java b/src/java/main/org/apache/zookeeper/ClientCnxnSocket.java
index 22f6962..5659266 100644
--- a/src/java/main/org/apache/zookeeper/ClientCnxnSocket.java
+++ b/src/java/main/org/apache/zookeeper/ClientCnxnSocket.java
@@ -120,7 +120,7 @@ abstract class ClientCnxnSocket {
buf.append(Integer.toHexString(b) + ",");
}
buf.append("]");
- LOG.trace("readConnectRestult " + incomingBuffer.remaining() + " "
+ LOG.trace("readConnectResult " + incomingBuffer.remaining() + " "
+ buf.toString());
}
ByteBufferInputStream bbis = new ByteBufferInputStream(incomingBuffer);
@@ -151,7 +151,7 @@ abstract class ClientCnxnSocket {
abstract void enableReadWriteOnly();
abstract void doTransport(int waitTimeOut, List<Packet> pendingQueue,
- LinkedList<Packet> outgoingQueue) throws IOException,
+ LinkedList<Packet> outgoingQueue) throws IOException,
InterruptedException;
abstract void testableCloseSocket() throws IOException;
diff --git a/src/java/main/org/apache/zookeeper/ClientCnxnSocketNIO.java b/src/java/main/org/apache/zookeeper/ClientCnxnSocketNIO.java
index 0ec78a1..aa6a958 100644
--- a/src/java/main/org/apache/zookeeper/ClientCnxnSocketNIO.java
+++ b/src/java/main/org/apache/zookeeper/ClientCnxnSocketNIO.java
@@ -102,6 +102,8 @@ public class ClientCnxnSocketNIO extends ClientCnxnSocket {
if (!pbb.hasRemaining()) {
sentCount++;
Packet p = outgoingQueue.removeFirst();
+ // TODO: figure out why these 2 (Opcode.ping and .auth) are excluded from pendingQueue.
+ // (in other words, figure out what pendingQueue is).
if (p.requestHeader != null
&& p.requestHeader.getType() != OpCode.ping
&& p.requestHeader.getType() != OpCode.auth) {
@@ -243,7 +245,7 @@ public class ClientCnxnSocketNIO extends ClientCnxnSocket {
}
@Override
- void doTransport(int waitTimeOut, List<Packet> pendingQueue, LinkedList<Packet> outgoingQueue )
+ void doTransport(int waitTimeOut, List<Packet> pendingQueue, LinkedList<Packet> outgoingQueue)
throws IOException, InterruptedException {
selector.select(waitTimeOut);
Set<SelectionKey> selected;
@@ -272,7 +274,12 @@ public class ClientCnxnSocketNIO extends ClientCnxnSocket {
}
}
}
- if (sendThread.getZkState() == States.CONNECTED) {
+
+ if ((sendThread.getZkState() == States.CONNECTED)
+ ||
+ (sendThread.getZkState() == States.SASL_INITIAL)
+ ||
+ (sendThread.getZkState() == States.SASL)) {
if (outgoingQueue.size() > 0) {
enableWrite();
} else {
diff --git a/src/java/main/org/apache/zookeeper/ZooDefs.java b/src/java/main/org/apache/zookeeper/ZooDefs.java
index 832976f..c7bcfc4 100644
--- a/src/java/main/org/apache/zookeeper/ZooDefs.java
+++ b/src/java/main/org/apache/zookeeper/ZooDefs.java
@@ -54,6 +54,8 @@ public class ZooDefs {
public final int setWatches = 101;
+ public final int sasl = 102;
+
public final int createSession = -10;
public final int closeSession = -11;
diff --git a/src/java/main/org/apache/zookeeper/ZooKeeper.java b/src/java/main/org/apache/zookeeper/ZooKeeper.java
index 95ab4e7..0a70152 100644
--- a/src/java/main/org/apache/zookeeper/ZooKeeper.java
+++ b/src/java/main/org/apache/zookeeper/ZooKeeper.java
@@ -20,6 +20,8 @@ package org.apache.zookeeper;
import java.io.IOException;
import java.net.SocketAddress;
+import java.security.Principal;
+import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
@@ -64,6 +66,14 @@ import org.apache.zookeeper.proto.SetDataResponse;
import org.apache.zookeeper.proto.SyncRequest;
import org.apache.zookeeper.proto.SyncResponse;
import org.apache.zookeeper.server.DataTree;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
/**
* This is the main class of ZooKeeper client library. To use a ZooKeeper
@@ -320,7 +330,7 @@ public class ZooKeeper {
}
public enum States {
- CONNECTING, ASSOCIATING, CONNECTED, CLOSED, AUTH_FAILED;
+ CONNECTING, ASSOCIATING, CONNECTED, CLOSED, AUTH_FAILED, SASL_INITIAL, SASL;
public boolean isAlive() {
return this != CLOSED && this != AUTH_FAILED;
@@ -372,7 +382,8 @@ public class ZooKeeper {
* @throws IllegalArgumentException
* if an invalid chroot path is specified
*/
- public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
+ public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, Subject subject,
+ String service_principal)
throws IOException
{
LOG.info("Initiating client connection, connectString=" + connectString
@@ -380,16 +391,55 @@ public class ZooKeeper {
watchManager.defaultWatcher = watcher;
+ int indexOf = service_principal.indexOf("/");
+
+ String service_principal_name = service_principal.substring(0, indexOf);
+ String service_principal_hostname = service_principal.substring(indexOf+1,service_principal.length());
+
ConnectStringParser connectStringParser = new ConnectStringParser(
connectString);
HostProvider hostProvider = new StaticHostProvider(
connectStringParser.getServerAddresses());
+
+ SaslClient saslClient = createSaslClient(subject,service_principal_name,service_principal_hostname);
+
cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
hostProvider, sessionTimeout, this, watchManager,
- getClientCnxnSocket());
+ getClientCnxnSocket(),subject, saslClient);
cnxn.start();
}
+ // CallbackHandler here refers to javax.security.auth.callback.CallbackHandler.
+ // (not to be confused with packet callbacks like ServerSaslResponseCallback, defined above).
+ private static class ClientCallbackHandler implements CallbackHandler {
+ @Override
+ public void handle(Callback[] callbacks) throws
+ UnsupportedCallbackException {
+ System.out.println("ClientCallbackHandler::handle()");
+ AuthorizeCallback ac = null;
+ for (Callback callback : callbacks) {
+ if (callback instanceof AuthorizeCallback) {
+ ac = (AuthorizeCallback) callback;
+ } else {
+ throw new UnsupportedCallbackException(callback,
+ "Unrecognized SASL GSSAPI Callback");
+ }
+ }
+ if (ac != null) {
+ String authid = ac.getAuthenticationID();
+ String authzid = ac.getAuthorizationID();
+ if (authid.equals(authzid)) {
+ ac.setAuthorized(true);
+ } else {
+ ac.setAuthorized(false);
+ }
+ if (ac.isAuthorized()) {
+ ac.setAuthorizedID(authzid);
+ }
+ }
+ }
+ }
+
/**
* To create a ZooKeeper client object, the application needs to pass a
* connection string containing a comma separated list of host:port pairs,
@@ -443,7 +493,8 @@ public class ZooKeeper {
* @throws IllegalArgumentException for an invalid list of ZooKeeper hosts
*/
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
- long sessionId, byte[] sessionPasswd)
+ long sessionId, byte[] sessionPasswd, Subject subject,
+ String server_principal,String client_principal, String service_principal_hostname)
throws IOException
{
LOG.info("Initiating client connection, connectString=" + connectString
@@ -459,12 +510,48 @@ public class ZooKeeper {
connectString);
HostProvider hostProvider = new StaticHostProvider(
connectStringParser.getServerAddresses());
+
+ SaslClient saslClient = createSaslClient(subject,server_principal,service_principal_hostname);
+
cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
- hostProvider, sessionTimeout, this, watchManager,
- getClientCnxnSocket(), sessionId, sessionPasswd);
+ hostProvider, sessionTimeout, this, watchManager,
+ getClientCnxnSocket(), sessionId, sessionPasswd, subject, saslClient);
cnxn.start();
}
+ private static SaslClient createSaslClient(Subject subject, final String serviceName, final String serviceHostname) {
+
+ // determine client principal from subject.
+ try {
+ final Object[] principals = subject.getPrincipals().toArray();
+ final Principal clientPrincipal = (Principal)principals[0];
+ final String clientPrincipalName = clientPrincipal.getName();
+
+ SaslClient saslClient = null;
+ try {
+ saslClient = Subject.doAs(subject,new PrivilegedExceptionAction<SaslClient>() {
+ public SaslClient run() throws SaslException {
+ // TODO: should depend on CLI arguments.
+ String[] mechs = {"GSSAPI"};
+ LOG.debug("creating sasl client: client="+clientPrincipalName+";service="+serviceName+";serviceHostname="+serviceHostname);
+ SaslClient saslClient = Sasl.createSaslClient(mechs,clientPrincipalName,serviceName,serviceHostname,null,new ClientCallbackHandler());
+ return saslClient;
+ }
+ });
+ return saslClient;
+ }
+ catch (Exception e) {
+ LOG.error("Error creating SASL client:" + e);
+ e.printStackTrace();
+ return null;
+ }
+ }
+ catch (NullPointerException e) {
+ LOG.error("Fatal zookeeper client error: no principals found for server.");
+ System.exit(-1);
+ return null;
+ }
+ }
/**
* The session id for this ZooKeeper client instance. The value returned is
* not valid until the client connects to a server and may change after a
diff --git a/src/java/main/org/apache/zookeeper/ZooKeeperMain.java b/src/java/main/org/apache/zookeeper/ZooKeeperMain.java
index f70d0e2..0d76bf1 100644
--- a/src/java/main/org/apache/zookeeper/ZooKeeperMain.java
+++ b/src/java/main/org/apache/zookeeper/ZooKeeperMain.java
@@ -31,6 +31,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.NoSuchElementException;
import org.apache.log4j.Logger;
@@ -40,6 +41,16 @@ import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
+import java.security.Principal;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+
+
/**
* The command line client to ZooKeeper.
*
@@ -55,6 +66,7 @@ public class ZooKeeperMain {
protected ZooKeeper zk;
protected String host = "";
+ protected Subject subject = null;
public boolean getPrintWatches( ) {
return printWatches;
@@ -194,6 +206,8 @@ public class ZooKeeperMain {
options.put("server", it.next());
} else if (opt.equals("-timeout")) {
options.put("timeout", it.next());
+ } else if (opt.equals("-jaas")) {
+ options.put("jaas", it.next());
}
} catch (NoSuchElementException e){
System.err.println("Error: no argument found for option "
@@ -258,10 +272,21 @@ public class ZooKeeperMain {
if (zk != null && zk.getState().isAlive()) {
zk.close();
}
- host = newHost;
- zk = new ZooKeeper(host,
+
+ this.subject = JAASLogin();
+
+ int indexOf = newHost.indexOf(":");
+
+ String zkHostname = newHost.substring(0,indexOf);
+
+ // default service principal name is zookeeper/ (service name/host name)
+ String servicePrincipalName = "zookeeper/"+zkHostname;
+
+ zk = new ZooKeeper(newHost,
Integer.parseInt(cl.getOption("timeout")),
- new MyWatcher());
+ new MyWatcher(),
+ this.subject,
+ servicePrincipalName);
}
public static void main(String args[])
@@ -271,9 +296,40 @@ public class ZooKeeperMain {
main.run();
}
+ public Subject JAASLogin() {
+ Subject subject = null;
+ try {
+ final String CLIENT_SECTION_OF_JAAS_CONF_FILE = "Client"; // The section (of the JAAS configuration file named $JAAS_CONF_FILE_NAME)
+
+ if (System.getProperty("java.security.auth.login.config") != null) {
+ LOG.info("Using JAAS configuration file: " + System.getProperty("java.security.auth.login.config"));
+ }
+ else {
+ if (cl.getOption("jaas") != null) {
+ System.setProperty("java.security.auth.login.config",cl.getOption("jaas"));
+ }
+ else {
+ LOG.warn("No JAAS conf file supplied: continuing without SASL authentication.");
+ return null;
+ }
+
+ }
+ LoginContext loginCtx = null;
+ loginCtx = new LoginContext(CLIENT_SECTION_OF_JAAS_CONF_FILE,
+ new LoginCallbackHandler());
+ loginCtx.login();
+ subject = loginCtx.getSubject();
+ }
+ catch (LoginException e) {
+ LOG.error("Login failure : " + e + "; continuing without SASL authentication.");
+ }
+ return subject;
+ }
+
+
public ZooKeeperMain(String args[]) throws IOException, InterruptedException {
cl.parseOptions(args);
- System.out.println("Connecting to " + cl.getOption("server"));
+
connectToZK(cl.getOption("server"));
//zk = new ZooKeeper(cl.getOption("server"),
// Integer.parseInt(cl.getOption("timeout")), new MyWatcher());
@@ -648,7 +704,7 @@ public class ZooKeeperMain {
if (args.length >=2) {
connectToZK(args[1]);
} else {
- connectToZK(host);
+ connectToZK(host);
}
}
@@ -837,4 +893,25 @@ public class ZooKeeperMain {
}
return acl;
}
+
+
+ private class LoginCallbackHandler implements CallbackHandler {
+
+ public LoginCallbackHandler() {
+ super();
+ }
+
+ // no callbacks supported: use the ticket cache instead.
+ // (command line 'kinit'; and set useTicketCache=true in your JAAS configuration file).
+ public void handle( Callback[] callbacks)
+ throws IOException, UnsupportedCallbackException {
+
+ for ( int i=0; i<callbacks.length; i++) {
+ throw new UnsupportedCallbackException(
+ callbacks[i], "Unrecognized Callback");
+ }
+ }
+
+ }
}
+
diff --git a/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java b/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java
index 613e3a3..f3ae553 100644
--- a/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java
+++ b/src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java
@@ -30,29 +30,19 @@ import org.apache.zookeeper.KeeperException.Code;
import org.apache.zookeeper.KeeperException.SessionMovedException;
import org.apache.zookeeper.ZooDefs.OpCode;
import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
-import org.apache.zookeeper.proto.CreateResponse;
-import org.apache.zookeeper.proto.ExistsRequest;
-import org.apache.zookeeper.proto.ExistsResponse;
-import org.apache.zookeeper.proto.GetACLRequest;
-import org.apache.zookeeper.proto.GetACLResponse;
-import org.apache.zookeeper.proto.GetChildren2Request;
-import org.apache.zookeeper.proto.GetChildren2Response;
-import org.apache.zookeeper.proto.GetChildrenRequest;
-import org.apache.zookeeper.proto.GetChildrenResponse;
-import org.apache.zookeeper.proto.GetDataRequest;
-import org.apache.zookeeper.proto.GetDataResponse;
-import org.apache.zookeeper.proto.ReplyHeader;
-import org.apache.zookeeper.proto.SetACLResponse;
-import org.apache.zookeeper.proto.SetDataResponse;
-import org.apache.zookeeper.proto.SetWatches;
-import org.apache.zookeeper.proto.SyncRequest;
-import org.apache.zookeeper.proto.SyncResponse;
+import org.apache.zookeeper.proto.*;
+
+
import org.apache.zookeeper.server.DataTree.ProcessTxnResult;
import org.apache.zookeeper.server.ZooKeeperServer.ChangeRecord;
import org.apache.zookeeper.txn.CreateSessionTxn;
import org.apache.zookeeper.txn.ErrorTxn;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
+
/**
* This Request processor actually applies any transaction associated with a
* request and services any queries. It is always at the end of a
@@ -323,6 +313,42 @@ public class FinalRequestProcessor implements RequestProcessor {
rsp = new GetChildren2Response(children, stat);
break;
}
+ case OpCode.sasl: {
+ // client sent a SASL token: respond with our own SASL token in response.
+ LOG.info("FinalRequestProcessor:ProcessRequest():Responding to client SASL token.");
+ lastOp = "SASL";
+
+ GetSASLRequest clientTokenRecord = new GetSASLRequest();
+ ZooKeeperServer.byteBuffer2Record(request.request,clientTokenRecord);
+
+ // Overloading GetDataRequest()'s path field to hold the client token.
+ byte[] clientToken = clientTokenRecord.getToken();
+ LOG.info("Size of client SASL token: " + clientToken.length);
+ byte[] responseToken = null;
+
+ try {
+ SaslServer saslServer = cnxn.saslServer;
+ try {
+ responseToken = saslServer.evaluateResponse(clientToken);
+
+ if (saslServer.isComplete() == true) {
+ cnxn.addAuthInfo(new Id("sasl",saslServer.getAuthorizationID()));
+ }
+ }
+ catch (SaslException e) {
+ LOG.error("saslServer.evaluateResponse() saslException:" + e);
+ e.printStackTrace();
+ cnxn.sendCloseSession();
+ }
+
+ }
+ catch (NullPointerException e) {
+ LOG.error("cnxn.saslServer is null: cnxn object did not initialize its saslServer properly.");
+ }
+
+ rsp = new SetSASLResponse(responseToken);
+ break;
+ }
}
} catch (SessionMovedException e) {
// session moved is a connection level error, we need to tear
diff --git a/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java b/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java
index 47a23bf..18ae56c 100644
--- a/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java
+++ b/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java
@@ -35,7 +35,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
-
import org.apache.jute.BinaryInputArchive;
import org.apache.jute.BinaryOutputArchive;
import org.apache.jute.Record;
@@ -97,6 +96,8 @@ public class NIOServerCnxn extends ServerCnxn {
this.sock = sock;
this.sk = sk;
this.factory = factory;
+ this.saslServer = factory.createSaslServer();
+
if (zk != null) {
outstandingLimit = zk.getGlobalOutstandingLimit();
}
@@ -475,7 +476,7 @@ public class NIOServerCnxn extends ServerCnxn {
/**
* Set of threads for commmand ports. All the 4
* letter commands are run via a thread. Each class
- * maps to a correspoding 4 letter command. CommandThread
+ * maps to a corresponding 4 letter command. CommandThread
* is the abstract class from which all the others inherit.
*/
private abstract class CommandThread extends Thread {
diff --git a/src/java/main/org/apache/zookeeper/server/NIOServerCnxnFactory.java b/src/java/main/org/apache/zookeeper/server/NIOServerCnxnFactory.java
index 9facbc7..64234de 100644
--- a/src/java/main/org/apache/zookeeper/server/NIOServerCnxnFactory.java
+++ b/src/java/main/org/apache/zookeeper/server/NIOServerCnxnFactory.java
@@ -34,6 +34,8 @@ import java.util.Set;
import org.apache.log4j.Logger;
+import javax.security.auth.Subject;
+
public class NIOServerCnxnFactory extends ServerCnxnFactory implements Runnable {
private static final Logger LOG = Logger.getLogger(NIOServerCnxnFactory.class);
@@ -72,7 +74,6 @@ public class NIOServerCnxnFactory extends ServerCnxnFactory implements Runnable
int maxClientCnxns = 10;
-
/**
* Construct a new server connection factory which will accept an unlimited number
* of concurrent connections from each client (up to the file descriptor
@@ -84,18 +85,22 @@ public class NIOServerCnxnFactory extends ServerCnxnFactory implements Runnable
Thread thread;
@Override
- public void configure(InetSocketAddress addr, int maxcc) throws IOException {
+ public void configure(InetSocketAddress addr, int maxcc, Subject subject) throws IOException {
thread = new Thread(this, "NIOServerCxn.Factory:" + addr);
thread.setDaemon(true);
maxClientCnxns = maxcc;
+ this.subject = subject;
this.ss = ServerSocketChannel.open();
ss.socket().setReuseAddress(true);
LOG.info("binding to port " + addr);
ss.socket().bind(addr);
ss.configureBlocking(false);
ss.register(selector, SelectionKey.OP_ACCEPT);
+
+
}
+
/** {@inheritDoc} */
public int getMaxClientCnxnsPerHost() {
return maxClientCnxns;
@@ -303,4 +308,6 @@ public class NIOServerCnxnFactory extends ServerCnxnFactory implements Runnable
return cnxns;
}
-}
\ No newline at end of file
+
+}
+
diff --git a/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java b/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
index 5c3a2bd..77780b2 100644
--- a/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
+++ b/src/java/main/org/apache/zookeeper/server/NettyServerCnxnFactory.java
@@ -44,6 +44,8 @@ import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.channel.group.DefaultChannelGroup;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import javax.security.auth.Subject;
+
public class NettyServerCnxnFactory extends ServerCnxnFactory {
Logger LOG = Logger.getLogger(NettyServerCnxnFactory.class);
@@ -298,7 +300,7 @@ public class NettyServerCnxnFactory extends ServerCnxnFactory {
}
@Override
- public void configure(InetSocketAddress addr, int maxClientCnxns)
+ public void configure(InetSocketAddress addr, int maxClientCnxns, Subject subject)
throws IOException
{
localAddress = addr;
diff --git a/src/java/main/org/apache/zookeeper/server/Request.java b/src/java/main/org/apache/zookeeper/server/Request.java
index 8537844..be52aa6 100644
--- a/src/java/main/org/apache/zookeeper/server/Request.java
+++ b/src/java/main/org/apache/zookeeper/server/Request.java
@@ -114,6 +114,7 @@ public class Request {
case OpCode.ping:
case OpCode.closeSession:
case OpCode.setWatches:
+ case OpCode.sasl:
return true;
default:
return false;
@@ -173,6 +174,8 @@ public class Request {
return "createSession";
case OpCode.closeSession:
return "closeSession";
+ case OpCode.sasl:
+ return "sasl";
case OpCode.error:
return "error";
default:
@@ -196,6 +199,7 @@ public class Request {
if (type != OpCode.createSession
&& type != OpCode.setWatches
&& type != OpCode.closeSession
+ && type != OpCode.sasl
&& request != null
&& request.remaining() >= 4)
{
diff --git a/src/java/main/org/apache/zookeeper/server/ServerCnxn.java b/src/java/main/org/apache/zookeeper/server/ServerCnxn.java
index 52acedb..5644fd9 100644
--- a/src/java/main/org/apache/zookeeper/server/ServerCnxn.java
+++ b/src/java/main/org/apache/zookeeper/server/ServerCnxn.java
@@ -36,6 +36,7 @@ import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.proto.ReplyHeader;
import org.apache.zookeeper.proto.RequestHeader;
+import javax.security.sasl.SaslServer;
/**
* Interface to a Server connection - represents a connection from a client
@@ -85,6 +86,8 @@ public abstract class ServerCnxn implements Stats, Watcher {
abstract void setSessionTimeout(int sessionTimeout);
+ protected SaslServer saslServer = null;
+
protected static class CloseRequestException extends IOException {
private static final long serialVersionUID = -7854505709816442681L;
@@ -430,3 +433,4 @@ public abstract class ServerCnxn implements Stats, Watcher {
}
}
+
diff --git a/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java b/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java
index 9ad7e9a..47165f2 100644
--- a/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java
+++ b/src/java/main/org/apache/zookeeper/server/ServerCnxnFactory.java
@@ -21,9 +21,22 @@ package org.apache.zookeeper.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import javax.management.JMException;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.SaslServer;
import org.apache.log4j.Logger;
import org.apache.zookeeper.jmx.MBeanRegistry;
@@ -35,9 +48,13 @@ public abstract class ServerCnxnFactory {
public interface PacketProcessor {
public void processPacket(ByteBuffer packet, ServerCnxn src);
}
-
+
Logger LOG = Logger.getLogger(ServerCnxnFactory.class);
+ Subject subject;
+
+ public Subject getSubject() { return subject; }
+
/**
* The buffer will cause the connection to be close when we do a send.
*/
@@ -50,7 +67,7 @@ public abstract class ServerCnxnFactory {
public abstract void closeSession(long sessionId);
public abstract void configure(InetSocketAddress addr,
- int maxClientCnxns) throws IOException;
+ int maxClientCnxns, Subject subject) throws IOException;
/** Maximum number of connections allowed from particular host (ip) */
public abstract int getMaxClientCnxnsPerHost();
@@ -75,6 +92,60 @@ public abstract class ServerCnxnFactory {
}
}
+ public SaslServer createSaslServer() {
+ // determine service principal name and hostname from zk server's subject.
+ try {
+ final Object[] principals = subject.getPrincipals().toArray();
+ final Principal servicePrincipal = (Principal)principals[0];
+
+ // e.g. servicePrincipalNameAndHostname := "zookeeper/myhost.foo.com@FOO.COM"
+ final String servicePrincipalNameAndHostname = servicePrincipal.getName();
+
+ int indexOf = servicePrincipalNameAndHostname.indexOf("/");
+
+ // e.g. servicePrincipalName := "zookeeper"
+ final String servicePrincipalName = servicePrincipalNameAndHostname.substring(0, indexOf);
+
+ // e.g. serviceHostnameAndKerbDomain := "myhost.foo.com@FOO.COM"
+ final String serviceHostnameAndKerbDomain = servicePrincipalNameAndHostname.substring(indexOf+1,servicePrincipalNameAndHostname.length());
+
+ indexOf = serviceHostnameAndKerbDomain.indexOf("@");
+ // e.g. serviceHostname := "myhost.foo.com"
+ final String serviceHostname = serviceHostnameAndKerbDomain.substring(0,indexOf);
+
+ final String mech = "GSSAPI"; // TODO: should depend on zoo.cfg specified mechs.
+
+ try {
+ return Subject.doAs(subject,new PrivilegedExceptionAction<SaslServer>() {
+ public SaslServer run() {
+ try {
+ SaslServer saslServer;
+ saslServer = Sasl.createSaslServer(mech, servicePrincipalName, serviceHostname, null, new SaslServerCallbackHandler());
+ return saslServer;
+ }
+ catch (SaslException e) {
+ LOG.error("Zookeeper Quorum Member failed to create a SaslServer to interact with a client during session initiation: " + e);
+ e.printStackTrace();
+ return null;
+ }
+ }
+ }
+ );
+ }
+ catch (PrivilegedActionException e) {
+ // TODO: exit server at this point(?)
+ e.printStackTrace();
+ }
+
+ }
+ catch (Exception e) {
+ LOG.error("server principal name/hostname figuring-out error: " + e);
+ }
+
+
+ return null;
+ }
+
public abstract void closeAll();
static public ServerCnxnFactory createFactory() throws IOException {
@@ -95,16 +166,16 @@ public abstract class ServerCnxnFactory {
}
static public ServerCnxnFactory createFactory(int clientPort,
- int maxClientCnxns) throws IOException
+ int maxClientCnxns, Subject subject) throws IOException
{
- return createFactory(new InetSocketAddress(clientPort), maxClientCnxns);
+ return createFactory(new InetSocketAddress(clientPort), maxClientCnxns, subject);
}
static public ServerCnxnFactory createFactory(InetSocketAddress addr,
- int maxClientCnxns) throws IOException
+ int maxClientCnxns, Subject subject) throws IOException
{
ServerCnxnFactory factory = createFactory();
- factory.configure(addr, maxClientCnxns);
+ factory.configure(addr, maxClientCnxns, subject);
return factory;
}
@@ -132,3 +203,40 @@ public abstract class ServerCnxnFactory {
}
}
+
+class SaslServerCallbackHandler implements CallbackHandler {
+ private static final Logger LOG = Logger.getLogger(CallbackHandler.class);
+
+ public void handle(Callback[] callbacks) throws
+ UnsupportedCallbackException {
+ LOG.debug("ServerCallbackHandler::handle()");
+ AuthorizeCallback ac = null;
+ for (Callback callback : callbacks) {
+ if (callback instanceof AuthorizeCallback) {
+ ac = (AuthorizeCallback) callback;
+ } else {
+ throw new UnsupportedCallbackException(callback,
+ "Unrecognized SASL GSSAPI Callback");
+ }
+ }
+ if (ac != null) {
+ 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");
+ ac.setAuthorizedID(authorizationID);
+ }
+ }
+ }
+}
+
+
diff --git a/src/java/main/org/apache/zookeeper/server/ServerConfig.java b/src/java/main/org/apache/zookeeper/server/ServerConfig.java
index ec710cd..33b649f 100644
--- a/src/java/main/org/apache/zookeeper/server/ServerConfig.java
+++ b/src/java/main/org/apache/zookeeper/server/ServerConfig.java
@@ -44,6 +44,7 @@ public class ServerConfig {
protected int minSessionTimeout = -1;
/** defaults to -1 if not set explicitly */
protected int maxSessionTimeout = -1;
+ protected String jaasConf;
/**
* Parse arguments for server configuration
@@ -95,6 +96,7 @@ public class ServerConfig {
maxClientCnxns = config.getMaxClientCnxns();
minSessionTimeout = config.getMinSessionTimeout();
maxSessionTimeout = config.getMaxSessionTimeout();
+ jaasConf = config.getJaasConf();
}
public InetSocketAddress getClientPortAddress() {
@@ -108,4 +110,6 @@ public class ServerConfig {
public int getMinSessionTimeout() { return minSessionTimeout; }
/** maximum session timeout in milliseconds, -1 if unset */
public int getMaxSessionTimeout() { return maxSessionTimeout; }
+
+ public String getJaasConf() { return jaasConf; }
}
diff --git a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java
index 8ad85d5..ef35c74 100644
--- a/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java
+++ b/src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java
@@ -22,6 +22,9 @@ import java.io.File;
import java.io.IOException;
import javax.management.JMException;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
import org.apache.log4j.Logger;
import org.apache.zookeeper.jmx.ManagedUtil;
@@ -106,8 +109,11 @@ public class ZooKeeperServerMain {
zkServer.setMinSessionTimeout(config.minSessionTimeout);
zkServer.setMaxSessionTimeout(config.maxSessionTimeout);
cnxnFactory = ServerCnxnFactory.createFactory();
+
+ Subject subject = setupSubject(config.getJaasConf());
cnxnFactory.configure(config.getClientPortAddress(),
- config.getMaxClientCnxns());
+ config.getMaxClientCnxns(),
+ subject);
cnxnFactory.startup(zkServer);
cnxnFactory.join();
if (zkServer.isRunning()) {
@@ -125,4 +131,53 @@ public class ZooKeeperServerMain {
protected void shutdown() {
cnxnFactory.shutdown();
}
+
+ protected Subject setupSubject(String jaasConf) {
+ // This initializes zkServerSubject.
+ // Should be called only once, at server startup time.
+ System.setProperty("javax.security.sasl.level","FINEST");
+ System.setProperty("handlers", "java.util.logging.ConsoleHandler");
+
+ Subject zkServerSubject;
+
+ if (System.getProperty("java.security.auth.login.config") != null) {
+ LOG.info("Using JAAS configuration file: " + System.getProperty("java.security.auth.login.config"));
+ }
+ else {
+ System.setProperty("java.security.auth.login.config",jaasConf);
+ }
+
+ //
+ // If using Kerberos, the file given in JAAS config file must have :
+ //
+ // $SERVICE_SECTION_OF_JAAS_CONF_FILE {
+ // com.sun.security.auth.module.Krb5LoginModule required
+ // useKeyTab=true
+ // keyTab="$KEY_TAB_FILE_NAME"
+ // doNotPrompt=true
+ // useTicketCache=false
+ // storeKey=true
+ // principal="$SERVICE_NAME/$HOST_NAME";
+ // };
+
+ try {
+ // 1. Service Login.
+ LoginContext loginCtx = null;
+ final String SERVICE_SECTION_OF_JAAS_CONF_FILE = "Server";
+ loginCtx = new LoginContext(SERVICE_SECTION_OF_JAAS_CONF_FILE);
+ loginCtx.login();
+ zkServerSubject = loginCtx.getSubject();
+ LOG.info("Zookeeper Quorum member successfully SASL-authenticated.");
+ return zkServerSubject;
+ }
+ catch (LoginException e) {
+ LOG.error("Zookeeper Quorum member failed to SASL-authenticate: " + e);
+ e.printStackTrace();
+ System.exit(-1);
+ }
+ return null;
+ }
+
+
+
}
diff --git a/src/java/main/org/apache/zookeeper/server/auth/SASLAuthenticationProvider.java b/src/java/main/org/apache/zookeeper/server/auth/SASLAuthenticationProvider.java
new file mode 100644
index 0000000..bfd9770
--- /dev/null
+++ b/src/java/main/org/apache/zookeeper/server/auth/SASLAuthenticationProvider.java
@@ -0,0 +1,37 @@
+package org.apache.zookeeper.server.auth;
+
+
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.server.ServerCnxn;
+
+public class SASLAuthenticationProvider implements AuthenticationProvider {
+ public String getScheme() {
+ return "sasl";
+ }
+
+ public KeeperException.Code
+ handleAuthentication(ServerCnxn cnxn, byte[] authData)
+ {
+ // Should never call this: SASL authentication is negotiated at session initiation.
+ // TODO: consider substituting current implementation of direct ClientCnxn manipulation with
+ // a call to this method (SASLAuthenticationProvider:handleAuthentication()) at session initiation.
+ return KeeperException.Code.AUTHFAILED;
+
+ }
+
+ public boolean matches(String id,String aclExpr) {
+ return id.equals(aclExpr);
+ }
+
+ public boolean isAuthenticated() {
+ // Should never be called.
+ return false;
+ }
+
+ public boolean isValid(String id) {
+ return true;
+ }
+
+
+}
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
index 7eb3684..38c8011 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
@@ -41,6 +41,8 @@ import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
import org.apache.zookeeper.server.quorum.flexible.QuorumMaj;
import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier;
+import javax.security.auth.Subject;
+
/**
* This class manages the quorum protocol. There are three states this server
* can be in:
@@ -447,12 +449,12 @@ public class QuorumPeer extends Thread implements QuorumStats.Provider {
*/
public QuorumPeer(Map<Long,QuorumServer> quorumPeers, File snapDir,
File logDir, int clientPort, int electionAlg,
- long myid, int tickTime, int initLimit, int syncLimit)
+ long myid, int tickTime, int initLimit, int syncLimit, Subject subject)
throws IOException
{
this(quorumPeers, snapDir, logDir, electionAlg,
myid,tickTime, initLimit,syncLimit,
- ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1),
+ ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1, subject),
new QuorumMaj(countParticipants(quorumPeers)));
}
@@ -463,12 +465,12 @@ public class QuorumPeer extends Thread implements QuorumStats.Provider {
public QuorumPeer(Map<Long,QuorumServer> quorumPeers, File snapDir,
File logDir, int clientPort, int electionAlg,
long myid, int tickTime, int initLimit, int syncLimit,
- QuorumVerifier quorumConfig)
+ QuorumVerifier quorumConfig, Subject subject)
throws IOException
{
this(quorumPeers, snapDir, logDir, electionAlg,
myid,tickTime, initLimit,syncLimit,
- ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1),
+ ServerCnxnFactory.createFactory(new InetSocketAddress(clientPort), -1, subject),
quorumConfig);
}
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
index 6904a48..0c8222a 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
@@ -71,6 +71,8 @@ public class QuorumPeerConfig {
protected LearnerType peerType = LearnerType.PARTICIPANT;
+ protected String jaasConf;
+
@SuppressWarnings("serial")
public static class ConfigException extends Exception {
public ConfigException(String msg) {
@@ -157,6 +159,8 @@ public class QuorumPeerConfig {
{
throw new ConfigException("Unrecognised peertype: " + value);
}
+ } else if (key.equals("jaasConf")) {
+ jaasConf = value;
} else if (key.startsWith("server.")) {
int dot = key.indexOf('.');
long sid = Long.parseLong(key.substring(dot + 1));
@@ -378,4 +382,8 @@ public class QuorumPeerConfig {
public LearnerType getPeerType() {
return peerType;
}
+
+ public String getJaasConf() {
+ return jaasConf;
+ }
}
diff --git a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
index ffeb90d..1aa1439 100644
--- a/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
+++ b/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
@@ -21,6 +21,7 @@ import java.io.File;
import java.io.IOException;
import javax.management.JMException;
+import javax.security.auth.Subject;
import org.apache.log4j.Logger;
import org.apache.zookeeper.jmx.ManagedUtil;
@@ -119,8 +120,10 @@ public class QuorumPeerMain {
LOG.info("Starting quorum peer");
try {
ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory();
+ Subject subject = cnxnFactory.getSubject();
cnxnFactory.configure(config.getClientPortAddress(),
- config.getMaxClientCnxns());
+ config.getMaxClientCnxns(),
+ subject);
quorumPeer = new QuorumPeer();
quorumPeer.setClientPortAddress(config.getClientPortAddress());
diff --git a/src/zookeeper.jute b/src/zookeeper.jute
index 7d96f32..0c8b00f 100644
--- a/src/zookeeper.jute
+++ b/src/zookeeper.jute
@@ -117,6 +117,15 @@ module org.apache.zookeeper.proto {
class SetDataResponse {
org.apache.zookeeper.data.Stat stat;
}
+ class GetSASLRequest {
+ buffer token;
+ }
+ class SetSASLRequest {
+ buffer token;
+ }
+ class SetSASLResponse {
+ buffer token;
+ }
class CreateRequest {
ustring path;
buffer data;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment