From 5ce035b369712369949b5d18a5a1fbc13e825b31 Mon Sep 17 00:00:00 2001
From: Damien Diederen
Date: Sun, 10 Nov 2019 18:11:38 +0100
Subject: [PATCH 1/4] ZooKeeperNet: Implement optional SASL authentication on
connect
---
src/dotnet/ZooKeeperNet/ClientConnection.cs | 43 ++++++++-
.../ClientConnectionRequestProducer.cs | 88 ++++++++++++++++++-
src/dotnet/ZooKeeperNet/ISaslClient.cs | 67 ++++++++++++++
src/dotnet/ZooKeeperNet/OpCode.cs | 1 +
src/dotnet/ZooKeeperNet/ZooKeeper.cs | 22 ++++-
src/dotnet/ZooKeeperNet/ZooKeeperNet.csproj | 13 +--
6 files changed, 217 insertions(+), 17 deletions(-)
create mode 100644 src/dotnet/ZooKeeperNet/ISaslClient.cs
diff --git a/src/dotnet/ZooKeeperNet/ClientConnection.cs b/src/dotnet/ZooKeeperNet/ClientConnection.cs
index b0394e423eb..54737f7f53c 100644
--- a/src/dotnet/ZooKeeperNet/ClientConnection.cs
+++ b/src/dotnet/ZooKeeperNet/ClientConnection.cs
@@ -107,6 +107,7 @@ public static bool DisableAutoWatchReset
internal string hosts;
internal readonly ZooKeeper zooKeeper;
internal readonly ZKWatchManager watcher;
+ internal readonly ISaslClient saslClient;
internal readonly List serverAddrs = new List();
internal readonly List authInfo = new List();
internal TimeSpan readTimeout;
@@ -122,7 +123,7 @@ public bool IsClosed
internal ClientConnectionRequestProducer producer;
internal ClientConnectionEventConsumer consumer;
-
+
///
/// Initializes a new instance of the class.
///
@@ -130,8 +131,9 @@ public bool IsClosed
/// The session timeout.
/// The zoo keeper.
/// The watch manager.
- public ClientConnection(string connectionString, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher):
- this(connectionString, sessionTimeout, zooKeeper, watcher, 0, new byte[16], DefaultConnectTimeout)
+ /// The SASL client.
+ public ClientConnection(string connectionString, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher, ISaslClient saslClient) :
+ this(connectionString, sessionTimeout, zooKeeper, watcher, saslClient, 0, new byte[16], DefaultConnectTimeout)
{
}
@@ -171,14 +173,47 @@ public ClientConnection(string hosts, TimeSpan sessionTimeout, ZooKeeper zooKeep
/// The session timeout.
/// The zoo keeper.
/// The watch manager.
+ /// The SASL client.
+ /// The session id.
+ /// The session passwd.
+ public ClientConnection(string hosts, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher, ISaslClient saslClient, long sessionId, byte[] sessionPasswd)
+ : this(hosts, sessionTimeout, zooKeeper, watcher, saslClient, 0, new byte[16], DefaultConnectTimeout)
+ {
+ }
+
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The hosts.
+ /// The session timeout.
+ /// The zoo keeper.
+ /// The watch manager.
+ /// The session id.
+ /// The session passwd.
+ /// Connection Timeout.
+ public ClientConnection(string hosts, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher, long sessionId, byte[] sessionPasswd, TimeSpan connectTimeout) :
+ this(hosts, sessionTimeout, zooKeeper, watcher, null, sessionId, sessionPasswd, connectTimeout)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The hosts.
+ /// The session timeout.
+ /// The zoo keeper.
+ /// The watch manager.
+ /// The SASL client.
/// The session id.
/// The session passwd.
/// Connection Timeout.
- public ClientConnection(string hosts, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher, long sessionId, byte[] sessionPasswd, TimeSpan connectTimeout)
+ public ClientConnection(string hosts, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher, ISaslClient saslClient, long sessionId, byte[] sessionPasswd, TimeSpan connectTimeout)
{
this.hosts = hosts;
this.zooKeeper = zooKeeper;
this.watcher = watcher;
+ this.saslClient = saslClient;
SessionTimeout = sessionTimeout;
SessionId = sessionId;
SessionPassword = sessionPasswd;
diff --git a/src/dotnet/ZooKeeperNet/ClientConnectionRequestProducer.cs b/src/dotnet/ZooKeeperNet/ClientConnectionRequestProducer.cs
index 4fad2eb23d9..255c079a204 100644
--- a/src/dotnet/ZooKeeperNet/ClientConnectionRequestProducer.cs
+++ b/src/dotnet/ZooKeeperNet/ClientConnectionRequestProducer.cs
@@ -441,6 +441,79 @@ private void PrimeConnection()
{
LOG.InfoFormat("Socket connection established to {0}, initiating session", client.Client.RemoteEndPoint);
ConnectRequest conReq = new ConnectRequest(0, lastZxid, Convert.ToInt32(conn.SessionTimeout.TotalMilliseconds), conn.SessionId, conn.SessionPassword);
+ Packet conPacket = new Packet(null, null, conReq, null, null, null, null, null);
+
+ if (conn.saslClient != null)
+ {
+ lock (outgoingQueue)
+ {
+ // SASL negociation is synchronous, and must complete before we send the (non-SASL) auth data and
+ // watches. We explicitly drive the send/receive as the queue processing loop is not active yet.
+
+ // First, push the ConnectRequest down the pipe.
+ DoSend(conPacket);
+ conPacket = null;
+
+ byte[] token = conn.saslClient.Start((IPEndPoint)client.Client.LocalEndPoint,
+ (IPEndPoint)client.Client.RemoteEndPoint);
+
+ try
+ {
+ bool lastPacket = false;
+
+ while (true)
+ {
+ RequestHeader h = new RequestHeader();
+ ReplyHeader r = new ReplyHeader();
+ h.Type = (int)OpCode.SASL;
+ GetSASLRequest request = new GetSASLRequest(token != null ? token : new byte[0]);
+ SetSASLResponse response = new SetSASLResponse();
+
+ Packet p = new Packet(h, r, request, response, null, null, null, null);
+
+ // Push the packet.
+ DoSend(p);
+
+ // Synchronously wait for the response.
+ if (!p.WaitUntilFinishedSlim(conn.ConnectionTimeout))
+ {
+ throw new TimeoutException(new StringBuilder("The request ").Append(request).Append(" timed out while waiting for a response from the server.").ToString());
+ }
+
+ if (r.Err != 0)
+ {
+ throw KeeperException.Create((KeeperException.Code)Enum.ToObject(typeof(KeeperException.Code), r.Err));
+ }
+
+ if (lastPacket)
+ {
+ break;
+ }
+
+ // SASL round.
+ token = conn.saslClient.EvaluateChallenge(response.Token);
+
+ if (conn.saslClient.IsCompleted)
+ {
+ if (conn.saslClient.HasLastPacket)
+ {
+ lastPacket = true;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+ finally
+ {
+ conn.saslClient.Finish();
+ }
+ }
+ }
+
+ bool hasOutgoingRequests;
lock (outgoingQueue)
{
@@ -459,10 +532,19 @@ private void PrimeConnection()
addPacketFirst(
new Packet(new RequestHeader(-4, (int) OpCode.Auth), null, new AuthPacket(0, id.Scheme, id.GetData()), null, null, null, null, null));
- addPacketFirst(new Packet(null, null, conReq, null, null, null, null, null));
-
+ // The ConnectRequest packet has already been sent in the SASL case.
+ if (conPacket != null)
+ {
+ addPacketFirst(conPacket);
+ conPacket = null;
+ }
+
+ hasOutgoingRequests = !outgoingQueue.IsEmpty();
}
- packetAre.Set();
+
+ if (hasOutgoingRequests)
+ packetAre.Set();
+
if (LOG.IsDebugEnabled)
LOG.DebugFormat("Session establishment request sent on {0}",client.Client.RemoteEndPoint);
}
diff --git a/src/dotnet/ZooKeeperNet/ISaslClient.cs b/src/dotnet/ZooKeeperNet/ISaslClient.cs
new file mode 100644
index 00000000000..7ee7548a52d
--- /dev/null
+++ b/src/dotnet/ZooKeeperNet/ISaslClient.cs
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+using System.Net;
+
+namespace ZooKeeperNet
+{
+ public interface ISaslClient
+ {
+ ///
+ /// Start an authentication session.
+ ///
+ ///
+ /// An initial, possibly empty, initial response to send to
+ /// the server.
+ ///
+ byte[] Start(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint);
+
+ ///
+ /// Determines whether the exchange has completed.
+ ///
+ /// Whether the exchange has completed.
+ bool IsCompleted { get; }
+
+ ///
+ /// Determines whether authentication using this client or
+ /// mechanism requires the emission of a "last packet," as
+ /// defined by ZooKeeper:
+ ///
+ /// "GSSAPI: server sends a final packet after authentication
+ /// succeeds or fails."
+ /// "non-GSSAPI: no final packet from server."
+ ///
+ /// https://github.com/apache/zookeeper/blob/11c07921c15e/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZooKeeperSaslClient.java#L285-L293
+ ///
+ /// Whether a "last packet" is required.
+ bool HasLastPacket { get; }
+
+ ///
+ /// Evaluates the challenge data and generate a response.
+ ///
+ /// The challenge sent from the server.
+ /// The response to send to the server.
+ byte[] EvaluateChallenge(byte[] challenge);
+
+ ///
+ /// Marks authentication as complete, allowing the client to
+ /// release resources which won't be needed until the next
+ /// .
+ ///
+ void Finish();
+ }
+}
diff --git a/src/dotnet/ZooKeeperNet/OpCode.cs b/src/dotnet/ZooKeeperNet/OpCode.cs
index f0c138b19b8..ed1ccc4cec9 100644
--- a/src/dotnet/ZooKeeperNet/OpCode.cs
+++ b/src/dotnet/ZooKeeperNet/OpCode.cs
@@ -33,6 +33,7 @@ public enum OpCode
GetChildren2 = 12,
Auth = 100,
SetWatches = 101,
+ SASL = 102,
CreateSession = -10,
CloseSession = -11,
Error = -1,
diff --git a/src/dotnet/ZooKeeperNet/ZooKeeper.cs b/src/dotnet/ZooKeeperNet/ZooKeeper.cs
index d89f437d58c..ddb6766fdc7 100644
--- a/src/dotnet/ZooKeeperNet/ZooKeeper.cs
+++ b/src/dotnet/ZooKeeperNet/ZooKeeper.cs
@@ -266,21 +266,35 @@ public override string ToString()
/// a watcher object which will be notified of state changes, may
/// also be notified for node events
///
- public ZooKeeper(string connectstring, TimeSpan sessionTimeout, IWatcher watcher)
+ public ZooKeeper(string connectstring, TimeSpan sessionTimeout, IWatcher watcher) :
+ this(connectstring, sessionTimeout, watcher, null)
+ {
+ }
+
+ ///
+ /// An optional object implementing the interface which will be used by the
+ /// to authenticate with the server immediately after (re)connect.
+ ///
+ public ZooKeeper(string connectstring, TimeSpan sessionTimeout, IWatcher watcher, ISaslClient saslClient)
{
LOG.InfoFormat("Initiating client connection, connectstring={0} sessionTimeout={1} watcher={2}", connectstring, sessionTimeout, watcher);
watchManager.DefaultWatcher = watcher;
- cnxn = new ClientConnection(connectstring, sessionTimeout, this, watchManager);
+ cnxn = new ClientConnection(connectstring, sessionTimeout, this, watchManager, saslClient);
cnxn.Start();
}
- public ZooKeeper(string connectstring, TimeSpan sessionTimeout, IWatcher watcher, long sessionId, byte[] sessionPasswd)
+ public ZooKeeper(string connectstring, TimeSpan sessionTimeout, IWatcher watcher, long sessionId, byte[] sessionPasswd) :
+ this(connectstring, sessionTimeout, watcher, null, sessionId, sessionPasswd)
+ {
+ }
+
+ public ZooKeeper(string connectstring, TimeSpan sessionTimeout, IWatcher watcher, ISaslClient saslClient, long sessionId, byte[] sessionPasswd)
{
LOG.InfoFormat("Initiating client connection, connectstring={0} sessionTimeout={1} watcher={2} sessionId={3} sessionPasswd={4}", connectstring, sessionTimeout, watcher, sessionId, (sessionPasswd == null ? "" : ""));
watchManager.DefaultWatcher = watcher;
- cnxn = new ClientConnection(connectstring, sessionTimeout, this, watchManager, sessionId, sessionPasswd);
+ cnxn = new ClientConnection(connectstring, sessionTimeout, this, watchManager, saslClient, sessionId, sessionPasswd);
cnxn.Start();
}
diff --git a/src/dotnet/ZooKeeperNet/ZooKeeperNet.csproj b/src/dotnet/ZooKeeperNet/ZooKeeperNet.csproj
index eb4a018b828..065e92f24c8 100644
--- a/src/dotnet/ZooKeeperNet/ZooKeeperNet.csproj
+++ b/src/dotnet/ZooKeeperNet/ZooKeeperNet.csproj
@@ -145,6 +145,7 @@
+
@@ -197,11 +198,11 @@
-
\ No newline at end of file
From fdbe16ffbafcf66f4f443f810fa914ce52902264 Mon Sep 17 00:00:00 2001
From: Damien Diederen
Date: Wed, 11 Dec 2019 14:18:14 +0100
Subject: [PATCH 2/4] ZooKeeperNet.Tests: Add CreateClientWithSasl utility
method
---
src/dotnet/ZooKeeperNet.Tests/AbstractZooKeeperTests.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/dotnet/ZooKeeperNet.Tests/AbstractZooKeeperTests.cs b/src/dotnet/ZooKeeperNet.Tests/AbstractZooKeeperTests.cs
index 55c82ef6867..71ce9e7a333 100644
--- a/src/dotnet/ZooKeeperNet.Tests/AbstractZooKeeperTests.cs
+++ b/src/dotnet/ZooKeeperNet.Tests/AbstractZooKeeperTests.cs
@@ -54,6 +54,12 @@ protected ZooKeeper CreateClientWithAddress(string address)
return new ZooKeeper(address, new TimeSpan(0, 0, 0, 10000), watcher);
}
+ protected virtual ZooKeeper CreateClientWithSasl(ISaslClient saslClient)
+ {
+ CountdownWatcher watcher = new CountdownWatcher();
+ return new ZooKeeper("127.0.0.1:2181", new TimeSpan(0, 0, 0, 10000), watcher, saslClient);
+ }
+
public class CountdownWatcher : IWatcher
{
readonly ManualResetEvent resetEvent = new ManualResetEvent(false);
From 450bcac96a092257974a57ae4add6267ec3b80e5 Mon Sep 17 00:00:00 2001
From: Damien Diederen
Date: Sun, 15 Dec 2019 15:03:22 +0100
Subject: [PATCH 3/4] S22.Sasl.dll: Add prebuilt SASL implementation
Built from this commit of https://github.com/ztzg/S22.Sasl:
commit b85c919b39ede184d36bc2e04f63b6e530ca3e10
Author: Damien Diederen
Date: Sun Dec 15 14:59:53 2019 +0100
S22.Sasl.csproj: Retarget to .NET v4.0
https://github.com/ztzg/S22.Sasl/tree/RT-46545-zookeeper-net-sasl-binary
---
src/dotnet/lib/S22.Sasl.dll | Bin 0 -> 74240 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100755 src/dotnet/lib/S22.Sasl.dll
diff --git a/src/dotnet/lib/S22.Sasl.dll b/src/dotnet/lib/S22.Sasl.dll
new file mode 100755
index 0000000000000000000000000000000000000000..c1107550cf41d2c7158fdfc91ed67cfe3122e1ca
GIT binary patch
literal 74240
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
ba(-9;e*2${fhg9Kbf(U({`
Date: Wed, 11 Dec 2019 14:18:37 +0100
Subject: [PATCH 4/4] ZooKeeperNet.Tests: Add DIGEST-MD5 SASL test, based on
S22.Sasl
Note that for this test to pass, The following must be configured in zoo.conf:
authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider
The following in jaas.conf:
Server {
org.apache.zookeeper.server.auth.DigestLoginModule required
user_super="adminsecret"
user_bob="bobsecret";
};
And the server must be started with:
-Djava.security.auth.login.config=.../jaas.conf
See https://cwiki.apache.org/confluence/display/ZOOKEEPER/Client-Server+mutual+authentication#Client-Servermutualauthentication-ServerConfiguration
for additional details.
---
src/dotnet/ZooKeeperNet.Tests/SaslTests.cs | 127 ++++++++++++++++++
.../ZooKeeperNet.Tests.csproj | 4 +
2 files changed, 131 insertions(+)
create mode 100755 src/dotnet/ZooKeeperNet.Tests/SaslTests.cs
diff --git a/src/dotnet/ZooKeeperNet.Tests/SaslTests.cs b/src/dotnet/ZooKeeperNet.Tests/SaslTests.cs
new file mode 100755
index 00000000000..930b2441ca6
--- /dev/null
+++ b/src/dotnet/ZooKeeperNet.Tests/SaslTests.cs
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+namespace ZooKeeperNet.Tests
+{
+ using System;
+ using NUnit.Framework;
+ using Org.Apache.Zookeeper.Data;
+ using ZooKeeperNet;
+ using S22.Sasl;
+ using System.Net;
+ using System.Collections.Generic;
+
+ class S22SaslClient : ISaslClient
+ {
+ // The following must be configured in zoo.conf:
+ //
+ // authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider
+ //
+ // The following in jaas.conf:
+ //
+ // Server {
+ // org.apache.zookeeper.server.auth.DigestLoginModule required
+ // user_super="adminsecret"
+ // user_bob="bobsecret";
+ // };
+ //
+ // And the server must be started with:
+ //
+ // -Djava.security.auth.login.config=.../jaas.conf
+ //
+ // See https://cwiki.apache.org/confluence/display/ZOOKEEPER/Client-Server+mutual+authentication#Client-Servermutualauthentication-ServerConfiguration
+ // for additional details.
+ private const string Username = "bob";
+ private const string Password = "bobsecret";
+
+ private SaslMechanism m = null;
+
+ public byte[] Start(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint)
+ {
+ m = SaslFactory.Create("DIGEST-MD5");
+
+ m.Properties.Add("Username", Username);
+ m.Properties.Add("Password", Password);
+ m.Properties.Add("Protocol", "zookeeper");
+
+ // Client start is empty.
+ return null;
+ }
+
+ public bool IsCompleted
+ {
+ get
+ {
+ return m == null || m.IsCompleted;
+ }
+ }
+
+ public bool HasLastPacket
+ {
+ get
+ {
+ return false; // not GSSAPI.
+ }
+ }
+
+ public byte[] EvaluateChallenge(byte[] token)
+ {
+ return m.GetResponse(token);
+ }
+
+ public void Finish()
+ {
+ m = null;
+ }
+ }
+
+ [TestFixture]
+ public class SaslTests : AbstractZooKeeperTests
+ {
+ [Test]
+ public void testSasl()
+ {
+ string name = "/" + Guid.NewGuid() + "sasltest";
+
+ using (var zk = CreateClientWithSasl(new S22SaslClient()))
+ {
+ List acl = new List();
+ acl.Add(new ACL(Perms.ALL, new ZKId("sasl", "bob")));
+
+ Assert.AreEqual(name, zk.Create(name, new byte[0], acl, CreateMode.Persistent));
+ }
+
+ using (var zk = CreateClient())
+ {
+ try
+ {
+ zk.GetData(name, false, new Stat());
+ Assert.Fail("Should have received a permission error");
+ }
+ catch (KeeperException e)
+ {
+ Assert.AreEqual(KeeperException.Code.NOAUTH, e.ErrorCode);
+ }
+ }
+
+ using (var zk = CreateClientWithSasl(new S22SaslClient()))
+ {
+ zk.GetData(name, false, new Stat());
+ }
+ }
+ }
+}
diff --git a/src/dotnet/ZooKeeperNet.Tests/ZooKeeperNet.Tests.csproj b/src/dotnet/ZooKeeperNet.Tests/ZooKeeperNet.Tests.csproj
index 473173b9248..4b37c271f8a 100644
--- a/src/dotnet/ZooKeeperNet.Tests/ZooKeeperNet.Tests.csproj
+++ b/src/dotnet/ZooKeeperNet.Tests/ZooKeeperNet.Tests.csproj
@@ -66,6 +66,9 @@
False
..\lib\pnunit.framework.dll
+
+ ..\lib\S22.Sasl.dll
+
3.5
@@ -77,6 +80,7 @@
+