Created
January 26, 2011 02:35
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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