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
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<!-- Packages only used in the solution, upgrade at will -->
<PackageVersion Include="BenchmarkDotNet" Version="0.13.1" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.0.0-alpha" />
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4-beta1.22362.3" />
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
Expand Down
1 change: 1 addition & 0 deletions StackExchange.Redis.sln
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Basic", "Basic", "{38BDEEED
tests\RedisConfigs\Basic\primary-6379.conf = tests\RedisConfigs\Basic\primary-6379.conf
tests\RedisConfigs\Basic\replica-6380.conf = tests\RedisConfigs\Basic\replica-6380.conf
tests\RedisConfigs\Basic\secure-6381.conf = tests\RedisConfigs\Basic\secure-6381.conf
tests\RedisConfigs\Basic\tls-ciphers-6384.conf = tests\RedisConfigs\Basic\tls-ciphers-6384.conf
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicTestBaseline", "tests\BasicTestBaseline\BasicTestBaseline.csproj", "{8FDB623D-779B-4A84-BC6B-75106E41D8A4}"
Expand Down
14 changes: 8 additions & 6 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,26 +76,26 @@ The `ConfigurationOptions` object has a wide range of properties, all of which a
| abortConnect={bool} | `AbortOnConnectFail` | `true` (`false` on Azure) | If true, `Connect` will not create a connection while no servers are available |
| allowAdmin={bool} | `AllowAdmin` | `false` | Enables a range of commands that are considered risky |
| channelPrefix={string} | `ChannelPrefix` | `null` | Optional channel prefix for all pub/sub operations |
| checkCertificateRevocation={bool} | `CheckCertificateRevocation` | `true` | A Boolean value that specifies whether the certificate revocation list is checked during authentication. |
| checkCertificateRevocation={bool} | `CheckCertificateRevocation` | `true` | A Boolean value that specifies whether the certificate revocation list is checked during authentication. |
| connectRetry={int} | `ConnectRetry` | `3` | The number of times to repeat connect attempts during initial `Connect` |
| connectTimeout={int} | `ConnectTimeout` | `5000` | Timeout (ms) for connect operations |
| configChannel={string} | `ConfigurationChannel` | `__Booksleeve_MasterChanged` | Broadcast channel name for communicating configuration changes |
| configCheckSeconds={int} | `ConfigCheckSeconds` | `60` | Time (seconds) to check configuration. This serves as a keep-alive for interactive sockets, if it is supported. |
| configCheckSeconds={int} | `ConfigCheckSeconds` | `60` | Time (seconds) to check configuration. This serves as a keep-alive for interactive sockets, if it is supported. |
| defaultDatabase={int} | `DefaultDatabase` | `null` | Default database index, from `0` to `databases - 1` |
| keepAlive={int} | `KeepAlive` | `-1` | Time (seconds) at which to send a message to help keep sockets alive (60 sec default) |
| name={string} | `ClientName` | `null` | Identification for the connection within redis |
| password={string} | `Password` | `null` | Password for the redis server |
| user={string} | `User` | `null` | User for the redis server (for use with ACLs on redis 6 and above) |
| proxy={proxy type} | `Proxy` | `Proxy.None` | Type of proxy in use (if any); for example "twemproxy/envoyproxy" |
| resolveDns={bool} | `ResolveDns` | `false` | Specifies that DNS resolution should be explicit and eager, rather than implicit |
| serviceName={string} | `ServiceName` | `null` | Used for connecting to a sentinel primary service |
| serviceName={string} | `ServiceName` | `null` | Used for connecting to a sentinel primary service |
| ssl={bool} | `Ssl` | `false` | Specifies that SSL encryption should be used |
| sslHost={string} | `SslHost` | `null` | Enforces a particular SSL host identity on the server's certificate |
| sslProtocols={enum} | `SslProtocols` | `null` | Ssl/Tls versions supported when using an encrypted connection. Use '\|' to provide multiple values. |
| syncTimeout={int} | `SyncTimeout` | `5000` | Time (ms) to allow for synchronous operations |
| asyncTimeout={int} | `AsyncTimeout` | `SyncTimeout` | Time (ms) to allow for asynchronous operations |
| tiebreaker={string} | `TieBreaker` | `__Booksleeve_TieBreak` | Key to use for selecting a server in an ambiguous primary scenario |
| version={string} | `DefaultVersion` | (`3.0` in Azure, else `2.0`) | Redis version level (useful when the server does not make this available) |
| asyncTimeout={int} | `AsyncTimeout` | `SyncTimeout` | Time (ms) to allow for asynchronous operations |
| tiebreaker={string} | `TieBreaker` | `__Booksleeve_TieBreak` | Key to use for selecting a server in an ambiguous primary scenario |
| version={string} | `DefaultVersion` | (`4.0` in Azure, else `2.0`) | Redis version level (useful when the server does not make this available) |


Additional code-only options:
Expand All @@ -105,6 +105,8 @@ Additional code-only options:
- Determines how commands will be queued (or not) during a disconnect, for sending when it's available again
- BeforeSocketConnect - Default: `null`
- Allows modifying a `Socket` before connecting (for advanced scenarios)
- SslClientAuthenticationOptions (`netcooreapp3.1`/`net5.0` and higher) - Default: `null`
- Allows specifying exact options for SSL/TLS authentication against a server (e.g. cipher suites, protocols, etc.) - overrides all other SSL configuration options. This is a `Func<string, SslClientAuthenticationOptions>` which receiveces the host (or `SslHost` if set) to get the options for. If `null` is returned from the `Func`, it's the same as this property not being set at all when connecting.

Tokens in the configuration string are comma-separated; any without an `=` sign are assumed to be redis server endpoints. Endpoints without an explicit port will use 6379 if ssl is not enabled, and 6380 if ssl is enabled.
Tokens starting with `$` are taken to represent command maps, for example: `$config=cfg`.
Expand Down
2 changes: 2 additions & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- Performance: Optimization around timeout processing to reduce lock contention in the case of many items that haven't yet timed out during a heartbeat ([#2217 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2217))
- Fix [#2223](https://github.com/StackExchange/StackExchange.Redis/issues/2223): Resolve sync-context issues (missing `ConfigureAwait(false)`) ([#2229 by mgravell](https://github.com/StackExchange/StackExchange.Redis/pull/2229))
- Fix [#1968](https://github.com/StackExchange/StackExchange.Redis/issues/1968): Improved handling of EVAL scripts during server restarts and failovers, detecting and re-sending the script for a retry when needed ([#2170 by martintmk](https://github.com/StackExchange/StackExchange.Redis/pull/2170))
- Adds: `ConfigurationOptions.SslClientAuthenticationOptions` (`netcoreapp3.1`/`net5.0`+ only) to give more control over SSL/TLS authentication ([#2224 by NickCraver](https://github.com/StackExchange/StackExchange.Redis/pull/2224))


## 2.6.48

Expand Down
11 changes: 11 additions & 0 deletions src/StackExchange.Redis/ConfigurationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,14 @@ public int ResponseTimeout
/// </remarks>
public SocketManager? SocketManager { get; set; }

#if NETCOREAPP3_1_OR_GREATER
/// <summary>
/// A <see cref="SslClientAuthenticationOptions"/> provider for a given host, for custom TLS connection options.
/// Note: this overrides *all* other TLS and certificate settings, only for advanced use cases.
/// </summary>
public Func<string, SslClientAuthenticationOptions>? SslClientAuthenticationOptions { get; set; }
#endif

/// <summary>
/// Indicates whether the connection should be encrypted.
/// </summary>
Expand Down Expand Up @@ -619,6 +627,9 @@ public static ConfigurationOptions Parse(string configuration, bool ignoreUnknow
checkCertificateRevocation = checkCertificateRevocation,
BeforeSocketConnect = BeforeSocketConnect,
EndPoints = EndPoints.Clone(),
#if NETCOREAPP3_1_OR_GREATER
SslClientAuthenticationOptions = SslClientAuthenticationOptions,
#endif
};

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/StackExchange.Redis/ConnectionMultiplexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -557,8 +557,8 @@ private static bool AllComplete(Task[] tasks)
return true;
}

internal bool AuthSuspect { get; private set; }
internal void SetAuthSuspect() => AuthSuspect = true;
internal Exception? AuthException { get; private set; }
internal void SetAuthSuspect(Exception authException) => AuthException = authException;

/// <summary>
/// Creates a new <see cref="ConnectionMultiplexer"/> instance.
Expand Down
22 changes: 18 additions & 4 deletions src/StackExchange.Redis/ExceptionFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Authentication;
using System.Text;
using System.Threading;

Expand Down Expand Up @@ -383,17 +385,29 @@ private static string GetLabel(bool includeDetail, RedisCommand command, Message
internal static Exception UnableToConnect(ConnectionMultiplexer muxer, string? failureMessage = null)
{
var sb = new StringBuilder("It was not possible to connect to the redis server(s).");
if (muxer != null)
Exception? inner = null;
if (muxer is not null)
{
if (muxer.AuthSuspect) sb.Append(" There was an authentication failure; check that passwords (or client certificates) are configured correctly.");
else if (muxer.RawConfig.AbortOnConnectFail) sb.Append(" Error connecting right now. To allow this multiplexer to continue retrying until it's able to connect, use abortConnect=false in your connection string or AbortOnConnectFail=false; in your code.");
if (muxer.AuthException is Exception aex)
{
sb.Append(" There was an authentication failure; check that passwords (or client certificates) are configured correctly: (").Append(aex.GetType().Name).Append(") ").Append(aex.Message);
inner = aex;
if (aex is AuthenticationException && aex.InnerException is Exception iaex)
{
sb.Append(" (Inner - ").Append(iaex.GetType().Name).Append(") ").Append(iaex.Message);
}
}
else if (muxer.RawConfig.AbortOnConnectFail)
{
sb.Append(" Error connecting right now. To allow this multiplexer to continue retrying until it's able to connect, use abortConnect=false in your connection string or AbortOnConnectFail=false; in your code.");
}
}
if (!failureMessage.IsNullOrWhiteSpace())
{
sb.Append(' ').Append(failureMessage.Trim());
}

return new RedisConnectionException(ConnectionFailureType.UnableToConnect, sb.ToString());
return new RedisConnectionException(ConnectionFailureType.UnableToConnect, sb.ToString(), inner);
}

internal static Exception BeganProfilingWithDuplicateContext(object forContext)
Expand Down
14 changes: 13 additions & 1 deletion src/StackExchange.Redis/PhysicalConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1432,12 +1432,24 @@ internal async ValueTask<bool> ConnectedAsync(Socket socket, LogProxy? log, Sock
{
try
{
#if NETCOREAPP3_1_OR_GREATER
var configOptions = config.SslClientAuthenticationOptions?.Invoke(host);
if (configOptions is not null)
{
await ssl.AuthenticateAsClientAsync(configOptions);
}
else
{
ssl.AuthenticateAsClient(host, config.SslProtocols, config.CheckCertificateRevocation);
}
#else
ssl.AuthenticateAsClient(host, config.SslProtocols, config.CheckCertificateRevocation);
#endif
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
bridge.Multiplexer?.SetAuthSuspect();
bridge.Multiplexer?.SetAuthSuspect(ex);
throw;
}
log?.WriteLine($"TLS connection established successfully using protocol: {ssl.SslProtocol}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.get -> System.Func<string!, System.Net.Security.SslClientAuthenticationOptions!>?
StackExchange.Redis.ConfigurationOptions.SslClientAuthenticationOptions.set -> void
4 changes: 2 additions & 2 deletions src/StackExchange.Redis/ResultProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ public virtual bool SetResult(PhysicalConnection connection, Message message, in
}
if (result.IsError)
{
if (result.StartsWith(CommonReplies.NOAUTH)) bridge?.Multiplexer?.SetAuthSuspect();
if (result.StartsWith(CommonReplies.NOAUTH)) bridge?.Multiplexer?.SetAuthSuspect(new RedisServerException("NOAUTH Returned - connection has not authenticated"));

var server = bridge?.ServerEndPoint;
bool log = !message.IsInternalCall;
Expand Down Expand Up @@ -1112,7 +1112,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
SetResult(message, true);
return true;
}
if(message.Command == RedisCommand.AUTH) connection?.BridgeCouldBeNull?.Multiplexer?.SetAuthSuspect();
if(message.Command == RedisCommand.AUTH) connection?.BridgeCouldBeNull?.Multiplexer?.SetAuthSuspect(new RedisException("Unknown AUTH exception"));
return false;
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/StackExchange.Redis/StackExchange.Redis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
<PackageReference Include="System.IO.Compression" Condition="'$(TargetFramework)' == 'net472' or '$(TargetFramework)' == 'net461' " />
</ItemGroup>

<ItemGroup>
<!-- APIs for all target frameworks -->
<AdditionalFiles Include="PublicAPI/PublicAPI.Shipped.txt" />
<AdditionalFiles Include="PublicAPI/PublicAPI.Unshipped.txt" />
<!-- APIs for netcoreapp3.1+ -->
<AdditionalFiles Include="PublicAPI/netcoreapp3.1/PublicAPI.Shipped.txt" Condition="'$(TargetFramework)' == 'netcoreapp3.1' or '$(TargetFramework)' == 'net5.0'" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="StackExchange.Redis.Server, PublicKey=00240000048000009400000006020000002400005253413100040000010001007791a689e9d8950b44a9a8886baad2ea180e7a8a854f158c9b98345ca5009cdd2362c84f368f1c3658c132b3c0f74e44ff16aeb2e5b353b6e0fe02f923a050470caeac2bde47a2238a9c7125ed7dab14f486a5a64558df96640933b9f2b6db188fc4a820f96dce963b662fa8864adbff38e5b4542343f162ecdc6dad16912fff" />
<InternalsVisibleTo Include="StackExchange.Redis.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001007791a689e9d8950b44a9a8886baad2ea180e7a8a854f158c9b98345ca5009cdd2362c84f368f1c3658c132b3c0f74e44ff16aeb2e5b353b6e0fe02f923a050470caeac2bde47a2238a9c7125ed7dab14f486a5a64558df96640933b9f2b6db188fc4a820f96dce963b662fa8864adbff38e5b4542343f162ecdc6dad16912fff" />
Expand Down
11 changes: 11 additions & 0 deletions tests/RedisConfigs/Basic/tls-ciphers-6384.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
port 0
tls-port 6384
timeout 0
protected-mode no
tls-auth-clients no
tls-ciphers ECDHE-RSA-AES256-GCM-SHA384
tls-ciphersuites TLS_AES_256_GCM_SHA384
tls-protocols "TLSv1.2 TLSv1.3"
tls-cert-file /Certs/redis.crt
tls-key-file /Certs/redis.key
tls-ca-cert-file /Certs/ca.crt
29 changes: 29 additions & 0 deletions tests/RedisConfigs/Certs/ca.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE5jCCAs4CCQCCCQ8gWCbLVjANBgkqhkiG9w0BAQsFADA1MRMwEQYDVQQKDApS
ZWRpcyBUZXN0MR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjIw
ODI0MTMwOTEwWhcNMzIwODIxMTMwOTEwWjA1MRMwEQYDVQQKDApSZWRpcyBUZXN0
MR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDDuKJ+rUI5/MmOdc7PmhKlJrceqwnDsgyVtvV5X6Xn
P8OG2jSXSqpmQn32WCDOL0EldTYS5UPIE0dS7XOYKFJaZnlSZtKBVZaam1+T2mMv
/rNjk3qmJpNiFpjJbktEchiwrsF6l91gsNfRdc1XXku9nvLhjEyhpNRZ7NKLT+Vx
F7h3wkEqLJFwzaAxIPPyvt6aQsip5dRfExFSwCLY4PTGzsvfNNauWASFvgh+zk80
FFTeDm6AZRmMIgizUc+0JK46QposPZHZA4N9/wmNZ3gAGzIEXvIZ1A5Nn/xMmU/7
3IRdFkE6pZmaCLA5CwE2M8Z8WyYtPTwLGU9c5yjTKrcX69Dy1hzjyk3H+DsqObuR
rpEcCx6x9SlrJQb0zLcumeqNsXSLdLlUwOgGX/d78J3jYEMSwatnU9wTP26nWhXH
b37sQZz+kh9ZM9rlfhzij4eq/4QtDRzLN0G+y6uveujW+s2LXlhY73K6DP7ujUUW
tCYy0X+iw8YfXHYgYyoby84gYETg0kpR1bjUKQL2PNNf0BOKUjQF9K9IPIiQX0v7
0YFg/2Fs3fidTVPFCwiLGCQzmy6P9VZQ3EkblHcLtoNAaPieoXdX/s/wMXTqj/hU
b9jwmrqJ2sbEb6VBMrrIgCJqz52zQzE+64KgHCmrQR/ABTCUWhgnsDsUGmaHs8y6
cwIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCSg6CZEvBoRqrOCZ+m4z3pQzt/oRxZ
sUz9ZM94pumEOnbEV0AjObqq2LnNTfuyexguH+5HH4Dr2OTvDIL7xNMor7NSUXGA
yhiu+BxiS1tB1o6IyExwyjpbS61iqtUX09abSWP/+2UW1i8oEIuwUmkRlAxbxFWI
HN6+LFe1L32fbrbp90sRS96bljvxNBxGpYqcooLAHCbK2T6jHDAZF0cK5ByWZoJ4
FcD3tRYWelj8k80ZeoG4PIsCZylsSMPWeglbFqDV4gSpWx7nb4Pgpzs9REp02Cp0
4MWxAt2fmvPFn9xypeyo6gxZ+R2cmSKiu0sdVnp3u1RscH1aGnVJTpdygpuDYJQ7
hxn1Wv91zRi+h4MfVywSO/3gMIvdiJIiV7avgNEWiLXYUn2bb4gHzEMOrp2Z7BUp
/SwNHmikaWQj0MY6sOW7pOaistbokyyZw8TjgrTnS4GocN91h16JbuSgAI+Nrwsa
VcFvDCd7qSmJgeMfGhhlOdNenDGXsA9UVyTTkGfw3mgcm62uvewmj1I70ogk8M3z
khwAMI2OeagmHtXtjtg2BULM53IwFHJKV41B4rwekcMkboCsOfbhZwz42aLpT0YG
d0btKJkNcL7n8QiGtFmvreizrdyC5r23GNVnNdn2dhuJBqN65xJQoXh0x1PTnK7/
4IWfRo8kosNhmw==
-----END CERTIFICATE-----
Loading