Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/dotnet/ZooKeeperNet.Tests/AbstractZooKeeperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
127 changes: 127 additions & 0 deletions src/dotnet/ZooKeeperNet.Tests/SaslTests.cs
Original file line number Diff line number Diff line change
@@ -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> acl = new List<ACL>();
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());
}
}
}
}
4 changes: 4 additions & 0 deletions src/dotnet/ZooKeeperNet.Tests/ZooKeeperNet.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\lib\pnunit.framework.dll</HintPath>
</Reference>
<Reference Include="S22.Sasl">
<HintPath>..\lib\S22.Sasl.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
Expand All @@ -77,6 +80,7 @@
<Compile Include="ACLRootTests.cs" />
<Compile Include="AssemblyFixture.cs" />
<Compile Include="RecoveryTest.cs" />
<Compile Include="SaslTests.cs" />
<Compile Include="SocketTests.cs" />
<Compile Include="ChrootTests.cs" />
<Compile Include="ClientTests.cs" />
Expand Down
43 changes: 39 additions & 4 deletions src/dotnet/ZooKeeperNet/ClientConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IPEndPoint> serverAddrs = new List<IPEndPoint>();
internal readonly List<AuthData> authInfo = new List<AuthData>();
internal TimeSpan readTimeout;
Expand All @@ -122,16 +123,17 @@ public bool IsClosed

internal ClientConnectionRequestProducer producer;
internal ClientConnectionEventConsumer consumer;

/// <summary>
/// Initializes a new instance of the <see cref="ClientConnection"/> class.
/// </summary>
/// <param name="connectionString">The connection string.</param>
/// <param name="sessionTimeout">The session timeout.</param>
/// <param name="zooKeeper">The zoo keeper.</param>
/// <param name="watcher">The watch manager.</param>
public ClientConnection(string connectionString, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher):
this(connectionString, sessionTimeout, zooKeeper, watcher, 0, new byte[16], DefaultConnectTimeout)
/// <param name="saslClient">The SASL client.</param>
public ClientConnection(string connectionString, TimeSpan sessionTimeout, ZooKeeper zooKeeper, ZKWatchManager watcher, ISaslClient saslClient) :
this(connectionString, sessionTimeout, zooKeeper, watcher, saslClient, 0, new byte[16], DefaultConnectTimeout)
{
}

Expand Down Expand Up @@ -171,14 +173,47 @@ public ClientConnection(string hosts, TimeSpan sessionTimeout, ZooKeeper zooKeep
/// <param name="sessionTimeout">The session timeout.</param>
/// <param name="zooKeeper">The zoo keeper.</param>
/// <param name="watcher">The watch manager.</param>
/// <param name="saslClient">The SASL client.</param>
/// <param name="sessionId">The session id.</param>
/// <param name="sessionPasswd">The session passwd.</param>
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)
{
}


/// <summary>
/// Initializes a new instance of the <see cref="ClientConnection"/> class.
/// </summary>
/// <param name="hosts">The hosts.</param>
/// <param name="sessionTimeout">The session timeout.</param>
/// <param name="zooKeeper">The zoo keeper.</param>
/// <param name="watcher">The watch manager.</param>
/// <param name="sessionId">The session id.</param>
/// <param name="sessionPasswd">The session passwd.</param>
/// <param name="connectTimeout">Connection Timeout.</param>
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)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ClientConnection"/> class.
/// </summary>
/// <param name="hosts">The hosts.</param>
/// <param name="sessionTimeout">The session timeout.</param>
/// <param name="zooKeeper">The zoo keeper.</param>
/// <param name="watcher">The watch manager.</param>
/// <param name="saslClient">The SASL client.</param>
/// <param name="sessionId">The session id.</param>
/// <param name="sessionPasswd">The session passwd.</param>
/// <param name="connectTimeout">Connection Timeout.</param>
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;
Expand Down
88 changes: 85 additions & 3 deletions src/dotnet/ZooKeeperNet/ClientConnectionRequestProducer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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);
}
Expand Down
Loading