Skip to content

Commit

Permalink
NCBC-1451: ensure SelectBucket is called for every socket w/rbac
Browse files Browse the repository at this point in the history
Motivation
----------
When RBAC is used with 5.0 and greater clusters, SelectBucket operation
must be called on every connection made to the server or an UnknownError
will be returned for any K/V operations. This also applies to connections
created after bootstrapping - for example in the case that a node is
swapped in and the cluster rebalanced.

Modifications
-------------
In order to make this work, some refactoring/layering of existing code had
to be done. Also, some new properties for passing bucketName and the
connections state were added. I would consider this a temporary fix and a
more elegant solution should be considered in the future.

 - Added BucketName property to PoolConfiguration so that it can be passed
   into SelectBucket from the connection
 - Moved the SelectBucket code from the CCCP provider to the connection
   level.
 - Added CheckedForEnhancedAuthentication property to the connection so we
   can bypass the SelectBucket operation after its been checked.
 - Added SupportsEnhancedAuthentication property to ConnectionBase and
   IConnectionPool
 - Added code to set the SupportsEnhancedAuthentication property in the
   CTOR of SharedPooledIOService
 - Override IConnectionPool.Connections in SharedConnectionPool
 - "Fixed" SSL and RBAC

Results
-------
When using RBAC SelectBucket is now called on every connection.

Change-Id: I76d4a7aca20e2a68974f818a28a0255bcc3b805f
Reviewed-on: http://review.couchbase.org/80149
Tested-by: Build Bot <build@couchbase.com>
Reviewed-by: Mike Goldsmith <goldsmith.mike@gmail.com>
  • Loading branch information
jeffrymorris committed Jun 30, 2017
1 parent 4b3ef68 commit b7d5aa4
Show file tree
Hide file tree
Showing 16 changed files with 155 additions and 29 deletions.
61 changes: 49 additions & 12 deletions Src/Couchbase.IntegrationTests/Authentication/AuthenticatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using Castle.Core.Internal;
using Couchbase.Authentication;
using Couchbase.Configuration.Client;
using Couchbase.IntegrationTests.Utils;
using Couchbase.N1QL;
using Couchbase.Search;
Expand Down Expand Up @@ -144,8 +146,25 @@ public void PasswordAuthenticator_KV()
var bucket = cluster.OpenBucket("authenticated");
var result = bucket.Upsert("thekey", "thevalue");
Assert.IsTrue(result.Success);
}
}

[Test, Ignore("RBAC not available yet")]
public void PasswordAuthenticator_KV_SSL()
{
var authentictor = GetPasswordAuthenticator();
var config = TestConfiguration.GetCurrentConfiguration();
config.UseSsl = true;

ClientConfiguration.IgnoreRemoteCertificateNameMismatch = true;

var cluster = new Cluster(config);
cluster.Authenticate(authentictor);

var bucket = cluster.OpenBucket("authenticated");
var result = bucket.Upsert("thekey", "thevalue");
Assert.IsTrue(result.Success);
}

[Test, Ignore("RBAC not available yet")]
public void PasswordAuthenticator_N1QL()
{
Expand All @@ -160,8 +179,8 @@ public void PasswordAuthenticator_N1QL()
var result = bucket.Query<dynamic>(query);

Assert.IsTrue(result.Success);
}

}

[Test, Ignore("RBAC not available yet")]
public void PasswordAuthenticator_Views()
{
Expand All @@ -175,8 +194,8 @@ public void PasswordAuthenticator_Views()

Assert.IsFalse(result.Success);
Assert.AreEqual(HttpStatusCode.NotFound, result.StatusCode);
}

}

[Test, Ignore("RBAC not available yet")]
public void PasswordAuthenticator_FTS()
{
Expand All @@ -195,8 +214,8 @@ public void PasswordAuthenticator_FTS()

Assert.IsTrue(result.Success);
Assert.AreEqual(SearchStatus.Success, result.Status);
}

}

[Test, Ignore("RBAC not available yet")]
public void PasswordAuthenticator_ClusterManager()
{
Expand Down Expand Up @@ -241,8 +260,8 @@ public void PasswordAuthenticator_N1QL_Cluster()
var result = cluster.Query<dynamic>(query);

Assert.IsTrue(result.Success);
}

}

[Test, Ignore("RBAC not available yet")]
public void PasswordAuthenticator_Can_Connect_With_Username_From_ConnectionString()
{
Expand All @@ -264,11 +283,11 @@ public void PasswordAuthenticator_Can_Connect_With_Username_From_ConnectionStrin
}

#if NET45
[Test]
[Test, Ignore("RBAC not available yet")]
public void PasswordAuthenticator_Can_Auth_Using_ConfigSection()
{
var cluster = new Cluster(configurationSectionName: "couchbaseClients/basic");
var auth = new PasswordAuthenticator("authenticated", "secret");
var auth = new PasswordAuthenticator("test_user", "secure123");
cluster.Authenticate(auth);
Assert.IsNotNull(cluster.OpenBucket("authenticated"));
}
Expand All @@ -288,10 +307,28 @@ private static IAuthenticator GetClassicAuthenticator()
public void ClassicAuthenticator_KV()
{
var authenticator = GetClassicAuthenticator();

var cluster = new Cluster(TestConfiguration.GetCurrentConfiguration());
cluster.Authenticate(authenticator);

var bucket = cluster.OpenBucket("authenticated");
var result = bucket.Upsert("thekey", "thevalue");
Assert.IsTrue(result.Success);
}


[Test, Ignore("Requires SSL cert to be configured.")]
public void ClassicAuthenticator_KV_SSL()
{
var authenticator = GetClassicAuthenticator();
var config = TestConfiguration.GetCurrentConfiguration();
config.UseSsl = true;

//disable for testing
ClientConfiguration.IgnoreRemoteCertificateNameMismatch = true;

var cluster = new Cluster(config);
cluster.Authenticate(authenticator);

var bucket = cluster.OpenBucket("authenticated");
var result = bucket.Upsert("thekey", "thevalue");
Assert.IsTrue(result.Success);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public void When_UseConnectionPooling_Is_False_IOServiceFactory_Returns_SharedPo
var mockConnectionPool = new Mock<IConnectionPool>();
mockConnectionPool.Setup(x => x.Acquire()).Returns(mockConnection.Object);
mockConnectionPool.Setup(x => x.Configuration).Returns(new PoolConfiguration());
mockConnectionPool.Setup(x => x.Connections).Returns(new List<IConnection> {mockConnection.Object});

var service = clientConfig.IOServiceCreator.Invoke(mockConnectionPool.Object);

Expand All @@ -195,6 +196,7 @@ public void When_UseConnectionPooling_Is_True_IOServiceFactory_Returns_PooledIOS
var mockConnectionPool = new Mock<IConnectionPool>();
mockConnectionPool.Setup(x => x.Acquire()).Returns(mockConnection.Object);
mockConnectionPool.Setup(x => x.Configuration).Returns(new PoolConfiguration());
mockConnectionPool.Setup(x => x.Connections).Returns(new List<IConnection> { mockConnection.Object });

var service = clientConfig.IOServiceCreator.Invoke(mockConnectionPool.Object);

Expand All @@ -215,8 +217,10 @@ public void When_UseSsl_Is_True_IOServiceFactory_Returns_PooledIOService()
var connectionPool = new Mock<IConnectionPool>();
connectionPool.Setup(x => x.Acquire()).Returns(conn.Object);
connectionPool.Setup(x => x.Configuration).Returns(new PoolConfiguration());
connectionPool.Setup(x => x.Connections).Returns(new List<IConnection> { conn.Object });

var service = config.IOServiceCreator(connectionPool.Object);

Assert.IsInstanceOf<PooledIOService>(service);
}

Expand All @@ -234,6 +238,7 @@ public void When_UseSsl_Is_False_IOServiceFactory_Returns_SharedPooledIOService(
var connectionPool = new Mock<IConnectionPool>();
connectionPool.Setup(x => x.Acquire()).Returns(conn.Object);
connectionPool.Setup(x => x.Configuration).Returns(new PoolConfiguration());
connectionPool.Setup(x => x.Connections).Returns(new List<IConnection> { conn.Object });

var service = config.IOServiceCreator(connectionPool.Object);
Assert.IsInstanceOf<SharedPooledIOService>(service);
Expand All @@ -250,6 +255,7 @@ public void When_Defaults_Are_Used_IOServiceFactory_Returns_SharedPooledIOServic
var connectionPool = new Mock<IConnectionPool>();
connectionPool.Setup(x => x.Acquire()).Returns(conn.Object);
connectionPool.Setup(x => x.Configuration).Returns(new PoolConfiguration());
connectionPool.Setup(x => x.Connections).Returns(new List<IConnection> { conn.Object });

var service = config.IOServiceCreator(connectionPool.Object);
Assert.IsInstanceOf<SharedPooledIOService>(service);
Expand Down
4 changes: 4 additions & 0 deletions Src/Couchbase.UnitTests/IO/Services/PooledIOServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public void When_EnhanchedDurability_Is_True_Hello_Requests_MutationSeqNo()
var mockConnectionPool = new Mock<IConnectionPool>();
mockConnectionPool.Setup(x => x.Acquire()).Returns(mockConnection.Object);
mockConnectionPool.SetupGet(x => x.Configuration).Returns(new PoolConfiguration { UseEnhancedDurability = true });
mockConnectionPool.Setup(x => x.Connections).Returns(new List<IConnection> { mockConnection.Object });

var service = new PooledIOService(mockConnectionPool.Object);

Expand All @@ -44,6 +45,7 @@ public void When_EnhanchedDurability_Is_False_Hello_Doesnt_Requests_MutationSeqN
var mockConnectionPool = new Mock<IConnectionPool>();
mockConnectionPool.Setup(x => x.Acquire()).Returns(mockConnection.Object);
mockConnectionPool.SetupGet(x => x.Configuration).Returns(new PoolConfiguration { UseEnhancedDurability = false });
mockConnectionPool.Setup(x => x.Connections).Returns(new List<IConnection> { mockConnection.Object });

var service = new PooledIOService(mockConnectionPool.Object);

Expand Down Expand Up @@ -83,6 +85,7 @@ public void Result_Has_Failure_Status_If_ErrorMap_Available()
var mockConnectionPool = new Mock<IConnectionPool>();
mockConnectionPool.Setup(x => x.Acquire()).Returns(mockConnection.Object);
mockConnectionPool.SetupGet(x => x.Configuration).Returns(new PoolConfiguration());
mockConnectionPool.Setup(x => x.Connections).Returns(new List<IConnection> { mockConnection.Object });

var service = new PooledIOService(mockConnectionPool.Object)
{
Expand Down Expand Up @@ -119,6 +122,7 @@ public void Result_Has_UnknownError_Status_If_ErrorMap_Not_Available()
var mockConnectionPool = new Mock<IConnectionPool>();
mockConnectionPool.Setup(x => x.Acquire()).Returns(mockConnection.Object);
mockConnectionPool.SetupGet(x => x.Configuration).Returns(new PoolConfiguration());
mockConnectionPool.Setup(x => x.Connections).Returns(new List<IConnection> {mockConnection.Object});

var service = new PooledIOService(mockConnectionPool.Object)
{
Expand Down
2 changes: 2 additions & 0 deletions Src/Couchbase.UnitTests/IO/SharedConnectionPoolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ public void Authenticate()
{
throw new NotImplementedException();
}

public bool CheckedForEnhancedAuthentication { get; set; }
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions Src/Couchbase/Configuration/Client/PoolConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ private static void ValidateConnectionValues(int maxSize, int minSize)
ExceptionUtil.PoolConfigMaxGreaterThanMin.WithParams(maxSize, minSize));
}
}

internal string BucketName { get; set; }
}
}

Expand Down
6 changes: 6 additions & 0 deletions Src/Couchbase/Configuration/CouchbaseConfigContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public override void LoadConfig(IBucketConfig bucketConfig, bool force = false)
{
var uri = UrlUtil.GetBaseUri(adapter, clientBucketConfig);
var poolConfiguration = ClientConfig.BucketConfigs[BucketConfig.Name].ClonePoolConfiguration(uri);
poolConfiguration.BucketName = BucketConfig.Name;

var connectionPool = ConnectionPoolFactory(poolConfiguration.Clone(uri), endpoint);
connectionPool.SaslMechanism = SaslFactory(BucketConfig.Name, BucketConfig.Password, connectionPool, Transcoder);
connectionPool.Initialize();
Expand Down Expand Up @@ -219,6 +221,8 @@ public void LoadConfig(IIOService ioService)
{
var uri = UrlUtil.GetBaseUri(adapter, clientBucketConfig);
var poolConfiguration = ClientConfig.BucketConfigs[BucketConfig.Name].ClonePoolConfiguration(uri);
poolConfiguration.BucketName = BucketConfig.Name;

var connectionPool = ConnectionPoolFactory(poolConfiguration.Clone(uri), endpoint);
connectionPool.Initialize();
connectionPool.SaslMechanism = SaslFactory(BucketConfig.Name, BucketConfig.Password, connectionPool, Transcoder);
Expand Down Expand Up @@ -311,6 +315,8 @@ public override void LoadConfig()
{
var uri = UrlUtil.GetBaseUri(adapter, clientBucketConfig);
var poolConfiguration = ClientConfig.BucketConfigs[BucketConfig.Name].ClonePoolConfiguration(uri);
poolConfiguration.BucketName = BucketConfig.Name;

var connectionPool = ConnectionPoolFactory(poolConfiguration.Clone(uri), endpoint);
connectionPool.SaslMechanism = SaslFactory(BucketConfig.Name, BucketConfig.Password, connectionPool, Transcoder);
connectionPool.Initialize();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,15 @@ public override IConfigInfo GetConfig(string bucketName, string username, string
try
{
var poolConfig = bucketConfiguration.ClonePoolConfiguration(server);
poolConfig.BucketName = bucketName;

var connectionPool = ConnectionPoolFactory(poolConfig, endPoint);
var saslMechanism = SaslFactory(username, password, connectionPool, Transcoder);
connectionPool.SaslMechanism = saslMechanism;
connectionPool.Initialize();

ioService = IOServiceFactory(connectionPool);

if (ioService.SupportsEnhancedAuthentication) // only execute this if RBAC is enabled on the cluster
{
var selectBucketResult = ioService.Execute(new SelectBucket(bucketName, Transcoder, ClientConfig.DefaultOperationLifespan));
if (!selectBucketResult.Success)
{
throw new AuthenticationException(string.Format("Authentication failed for bucket '{0}'", bucketName));
}
}

var operationResult = ioService.Execute(new Config(Transcoder, ClientConfig.DefaultOperationLifespan, endPoint));
if (operationResult.Success)
{
Expand Down
2 changes: 2 additions & 0 deletions Src/Couchbase/IO/ConnectionBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ public virtual void Authenticate()
{
//noop
}

public bool CheckedForEnhancedAuthentication { get; set; }
}
}

Expand Down
5 changes: 5 additions & 0 deletions Src/Couchbase/IO/ConnectionPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public override void Initialize()
var connection = Factory(this, Converter, BufferAllocator);

Authenticate(connection);
EnableEnhancedAuthentication(connection);

Log.Info("Initializing connection on [{0} | {1}] - {2} - Disposed: {3}",
EndPoint, connection.Identity, Identity, _disposed);
Expand Down Expand Up @@ -113,6 +114,7 @@ public override IConnection Acquire()
if (connection != null)
{
Authenticate(connection);
EnableEnhancedAuthentication(connection);
return connection;
}

Expand All @@ -124,6 +126,7 @@ public override IConnection Acquire()
if (connection != null)
{
Authenticate(connection);
EnableEnhancedAuthentication(connection);
return connection;
}

Expand All @@ -133,6 +136,8 @@ public override IConnection Acquire()
connection = Factory(this, Converter, BufferAllocator);

Authenticate(connection);
EnableEnhancedAuthentication(connection);

_refs.TryAdd(connection.Identity, connection);

Log.Debug("Acquire new: {0} | {1} | [{2}, {3}] - {4} - Disposed: {5}",
Expand Down
34 changes: 33 additions & 1 deletion Src/Couchbase/IO/ConnectionPoolBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
using Couchbase.Core;
using Couchbase.Logging;
using System.Security.Authentication;
using Couchbase.Core.Transcoders;
using Couchbase.IO.Converters;
using Couchbase.IO.Operations.Authentication;
using Couchbase.Utils;

namespace Couchbase.IO
Expand Down Expand Up @@ -46,7 +48,6 @@ internal ConnectionPoolBase(PoolConfiguration configuration, IPEndPoint endPoint
EndPoint = endPoint;
}


public abstract void Release(T connection);
public abstract void Dispose();
public abstract IConnection Acquire();
Expand Down Expand Up @@ -135,6 +136,37 @@ protected void Authenticate(IConnection connection)
}
}
}

/// <summary>
/// Gets a value indicating whether the server supports enhanced authentication for RBAC.
/// </summary>
/// <value>
/// <c>true</c> if the server supports enhanced authentication; otherwise, <c>false</c>.
/// </value>
public bool SupportsEnhancedAuthentication { get; set; }

/// <summary>
/// Enables enhanced authentication for RBAC on a connection.
/// </summary>
/// <param name="connection">The connection.</param>
/// <exception cref="AuthenticationException"></exception>
protected void EnableEnhancedAuthentication(IConnection connection)
{
if (SupportsEnhancedAuthentication && !connection.CheckedForEnhancedAuthentication) // only execute this if RBAC is enabled on the cluster
{
var selectBucketOp = new SelectBucket(Configuration.BucketName, new DefaultTranscoder(), 0);
var response = connection.Send(selectBucketOp.Write());
selectBucketOp.Read(response);

var selectBucketResult = selectBucketOp.GetResult();
connection.CheckedForEnhancedAuthentication = true;

if (!selectBucketResult.Success)
{
throw new AuthenticationException(string.Format("Authentication failed for bucket '{0}'", Configuration.BucketName));
}
}
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions Src/Couchbase/IO/IConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public interface IConnection : IDisposable
/// Authenticates this instance.
/// </summary>
void Authenticate();

bool CheckedForEnhancedAuthentication { get; set; }
}
}

Expand Down
8 changes: 8 additions & 0 deletions Src/Couchbase/IO/IConnectionPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,13 @@ public interface IConnectionPool : IDisposable
/// The sasl mechanism.
/// </value>
ISaslMechanism SaslMechanism { get; set; }

/// <summary>
/// Gets a value indicating whether the server supports enhanced authentication for RBAC.
/// </summary>
/// <value>
/// <c>true</c> if [supports enhanced authentication]; otherwise, <c>false</c>.
/// </value>
bool SupportsEnhancedAuthentication { get; set; }
}
}
1 change: 0 additions & 1 deletion Src/Couchbase/IO/IIOService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ public interface IIOService : IDisposable
/// </summary>
bool IsSecure { get; }


/// <summary>
/// Asynchrounously executes an operation for a given key.
/// </summary>
Expand Down
Loading

0 comments on commit b7d5aa4

Please sign in to comment.