Skip to content

Commit fe58b73

Browse files
authored
Merge branch 'main' into marc/aa2
2 parents 220d060 + a098426 commit fe58b73

123 files changed

Lines changed: 9893 additions & 1077 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/CI.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
paths:
88
- '**'
99
- '!/docs/*' # Don't run workflow when files are only in the /docs directory
10+
workflow_dispatch:
1011

1112
jobs:
1213
main:
@@ -22,14 +23,14 @@ jobs:
2223
fetch-depth: 0 # Fetch the full history
2324
- name: Start Redis Services (docker-compose)
2425
working-directory: ./tests/RedisConfigs
25-
run: docker compose -f docker-compose.yml up -d --wait
26+
run: docker compose -f docker-compose.yml up -d --wait
2627
- name: Install .NET SDK
2728
uses: actions/setup-dotnet@v3
2829
with:
29-
dotnet-version: |
30+
dotnet-version: |
3031
6.0.x
3132
8.0.x
32-
9.0.x
33+
10.0.x
3334
- name: .NET Build
3435
run: dotnet build Build.csproj -c Release /p:CI=true
3536
- name: StackExchange.Redis.Tests
@@ -144,8 +145,8 @@ jobs:
144145
reporter: dotnet-trx
145146
# Package and upload to MyGet only on pushes to main, not on PRs
146147
- name: .NET Pack
147-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
148+
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
148149
run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=${env:GITHUB_WORKSPACE}\.nupkgs /p:CI=true
149150
- name: Upload to MyGet
150-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
151-
run: dotnet nuget push ${env:GITHUB_WORKSPACE}\.nupkgs\*.nupkg -s https://www.myget.org/F/stackoverflow/api/v2/package -k ${{ secrets.MYGET_API_KEY }}
151+
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main'
152+
run: dotnet nuget push ${env:GITHUB_WORKSPACE}\.nupkgs\*.nupkg -s https://www.myget.org/F/stackoverflow/api/v2/package -k ${{ secrets.MYGET_API_KEY }}

.github/workflows/codeql.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
pull_request:
77
# The branches below must be a subset of the branches above
88
branches: [ 'main' ]
9+
workflow_dispatch:
10+
911
schedule:
1012
- cron: '8 9 * * 1'
1113

@@ -40,7 +42,7 @@ jobs:
4042

4143
# Initializes the CodeQL tools for scanning.
4244
- name: Initialize CodeQL
43-
uses: github/codeql-action/init@v3
45+
uses: github/codeql-action/init@v4
4446
with:
4547
languages: ${{ matrix.language }}
4648
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -55,6 +57,6 @@ jobs:
5557
run: dotnet build Build.csproj -c Release /p:CI=true
5658

5759
- name: Perform CodeQL Analysis
58-
uses: github/codeql-action/analyze@v3
60+
uses: github/codeql-action/analyze@v4
5961
with:
60-
category: "/language:${{matrix.language}}"
62+
category: "/language:${{matrix.language}}"

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
1111
<CodeAnalysisRuleset>$(MSBuildThisFileDirectory)Shared.ruleset</CodeAnalysisRuleset>
1212
<MSBuildWarningsAsMessages>NETSDK1069</MSBuildWarningsAsMessages>
13-
<NoWarn>$(NoWarn);NU5105;NU1507;SER001;SER002</NoWarn>
13+
<NoWarn>$(NoWarn);NU5105;NU1507;SER001;SER002;SER003</NoWarn>
1414
<PackageReleaseNotes>https://stackexchange.github.io/StackExchange.Redis/ReleaseNotes</PackageReleaseNotes>
1515
<PackageProjectUrl>https://stackexchange.github.io/StackExchange.Redis/</PackageProjectUrl>
1616
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1717

18-
<LangVersion>13</LangVersion>
18+
<LangVersion>14</LangVersion>
1919
<RepositoryType>git</RepositoryType>
2020
<RepositoryUrl>https://github.com/StackExchange/StackExchange.Redis/</RepositoryUrl>
2121

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
<PackageVersion Include="System.Threading.Channels" Version="5.0.0" />
99
<PackageVersion Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
1010
<PackageVersion Include="System.IO.Compression" Version="4.3.0" />
11-
<PackageVersion Include="System.IO.Hashing" Version="9.0.10" />
11+
<!-- note that this bumps System.Buffers, so is pinned in down-level in SE csproj -->
12+
<PackageVersion Include="System.IO.Hashing" Version="10.0.2" />
1213

1314
<!-- For analyzers, tied to the consumer's build SDK; at the moment, that means "us" -->
1415
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
StackExchange.Redis
22
===================
33

4-
StackExchange.Redis is a .NET client for communicating with RESP servers such as [Redis](https://redis.io/), [Garnet](https://microsoft.github.io/garnet/), [Valkey](https://valkey.io/), [Azure Cache for Redis](https://azure.microsoft.com/products/cache), [AWS ElastiCache](https://aws.amazon.com/elasticache/), and a wide range of other Redis-like servers. We do not maintain a list of compatible servers, but if the server has a Redis-like API: it will *probably* work fine. If not: log an issue with details!
4+
StackExchange.Redis is a .NET client for communicating with RESP servers such as [Redis](https://redis.io/), [Azure Managed Redis](https://azure.microsoft.com/products/managed-redis), [Garnet](https://microsoft.github.io/garnet/), [Valkey](https://valkey.io/), [AWS ElastiCache](https://aws.amazon.com/elasticache/), and a wide range of other Redis-like servers. We do not maintain a list of compatible servers, but if the server has a Redis-like API: it will *probably* work fine. If not: log an issue with details!
55

66
For all documentation, [see here](https://stackexchange.github.io/StackExchange.Redis/).
77

StackExchange.Redis.sln.DotSettings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
<s:Boolean x:Key="/Default/UserDictionary/Words/=keepttl/@EntryIndexedValue">True</s:Boolean>
1313
<s:Boolean x:Key="/Default/UserDictionary/Words/=lpush/@EntryIndexedValue">True</s:Boolean>
1414
<s:Boolean x:Key="/Default/UserDictionary/Words/=lrange/@EntryIndexedValue">True</s:Boolean>
15+
<s:Boolean x:Key="/Default/UserDictionary/Words/=psubscribe/@EntryIndexedValue">True</s:Boolean>
1516
<s:Boolean x:Key="/Default/UserDictionary/Words/=pubsub/@EntryIndexedValue">True</s:Boolean>
1617
<s:Boolean x:Key="/Default/UserDictionary/Words/=rpush/@EntryIndexedValue">True</s:Boolean>
18+
<s:Boolean x:Key="/Default/UserDictionary/Words/=spublish/@EntryIndexedValue">True</s:Boolean>
1719
<s:Boolean x:Key="/Default/UserDictionary/Words/=sscan/@EntryIndexedValue">True</s:Boolean>
20+
<s:Boolean x:Key="/Default/UserDictionary/Words/=ssubscribe/@EntryIndexedValue">True</s:Boolean>
1821
<s:Boolean x:Key="/Default/UserDictionary/Words/=vectorset/@EntryIndexedValue">True</s:Boolean>
1922
<s:Boolean x:Key="/Default/UserDictionary/Words/=xinfo/@EntryIndexedValue">True</s:Boolean>
2023
<s:Boolean x:Key="/Default/UserDictionary/Words/=xpending/@EntryIndexedValue">True</s:Boolean>

docs/Authentication.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Authentication
2+
3+
There are multiple ways of connecting to a Redis server, depending on the authentication model. The simplest
4+
(but least secure) approach is to use the `default` user, with no authentication, and no transport security.
5+
This is as simple as:
6+
7+
``` csharp
8+
var muxer = await ConnectionMultiplexer.ConnectAsync("myserver"); // or myserver:1241 to use a custom port
9+
```
10+
11+
This approach is often used for local transient servers - it is simple, but insecure. But from there,
12+
we can get more complex!
13+
14+
## TLS
15+
16+
If your server has TLS enabled, SE.Redis can be instructed to use it. In some cases (Azure Managed Redis, etc), the
17+
library will recognize the endpoint address, meaning: *you do not need to do anything*. To
18+
*manually* enable TLS, the `ssl` token can be used:
19+
20+
``` csharp
21+
var muxer = await ConnectionMultiplexer.ConnectAsync("myserver,ssl=true");
22+
```
23+
24+
This will work fine if the server is using a server-certificate that is already trusted by the local
25+
machine. If this is *not* the case, we need to tell the library about the server. This requires
26+
the `ConfigurationOptions` type:
27+
28+
``` csharp
29+
var options = ConfigurationOptions.Parse("myserver,ssl=true");
30+
// or: var options = new ConfigurationOptions { Endpoints = { "myserver" }, Ssl = true };
31+
// TODO configure
32+
var muxer = await ConnectionMultiplexer.ConnectAsync(options);
33+
```
34+
35+
If we have a local *issuer* public certificate (commonly `ca.crt`), we can use:
36+
37+
``` csharp
38+
options.TrustIssuer(caPath);
39+
```
40+
41+
Alternatively, in advanced scenarios: to provide your own custom server validation, the `options.CertificateValidation` callback
42+
can be used; this uses the normal [`RemoteCertificateValidationCallback`](https://learn.microsoft.com/dotnet/api/system.net.security.remotecertificatevalidationcallback)
43+
API.
44+
45+
## Usernames and Passwords
46+
47+
Usernames and passwords can be specified with the `user` and `password` tokens, respectively:
48+
49+
``` csharp
50+
var muxer = await ConnectionMultiplexer.ConnectAsync("myserver,ssl=true,user=myuser,password=mypassword");
51+
```
52+
53+
If no `user` is provided, the `default` user is assumed. In some cases, an authentication-token can be
54+
used in place of a classic password.
55+
56+
## Managed identities
57+
58+
If the server is an Azure Managed Redis resource, connections can be secured using Microsoft Entra ID authentication. Use the [Microsoft.Azure.StackExchangeRedis](https://github.com/Azure/Microsoft.Azure.StackExchangeRedis) extension package to handle the authentication using tokens retrieved from Microsoft Entra. The package integrates via the ConfigurationOptions class, and can use various types of identities for token retrieval. For example with a user-assigned managed identity:
59+
60+
```csharp
61+
var options = ConfigurationOptions.Parse("mycache.region.redis.azure.net:10000");
62+
await options.ConfigureForAzureWithUserAssignedManagedIdentityAsync(managedIdentityClientId);
63+
```
64+
65+
For details and samples see [https://github.com/Azure/Microsoft.Azure.StackExchangeRedis](https://github.com/Azure/Microsoft.Azure.StackExchangeRedis)
66+
67+
## Client certificates
68+
69+
If the server is configured to require a client certificate, this can be supplied in multiple ways.
70+
If you have a local public / private key pair (such as `MyUser2.crt` and `MyUser2.key`), the
71+
`options.SetUserPemCertificate(...)` method can be used:
72+
73+
``` csharp
74+
options.SetUserPemCertificate(
75+
userCertificatePath: userCrtPath,
76+
userKeyPath: userKeyPath
77+
);
78+
```
79+
80+
If you have a single `pfx` file that contains the public / private pair, the `options.SetUserPfxCertificate(...)`
81+
method can be used:
82+
83+
``` csharp
84+
options.SetUserPfxCertificate(
85+
userCertificatePath: userCrtPath,
86+
password: filePassword // optional
87+
);
88+
```
89+
90+
Alternatively, in advanced scenarios: to provide your own custom client-certificate lookup, the `options.CertificateSelection` callback
91+
can be used; this uses the normal
92+
[`LocalCertificateSelectionCallback`](https://learn.microsoft.com/dotnet/api/system.net.security.remotecertificatevalidationcallback)
93+
API.
94+
95+
## User certificates with implicit user authentication
96+
97+
Historically, the client certificate only provided access to the server, but as the `default` user. From 8.6,
98+
the server can be configured to use client certificates to provide user identity. This replaces the
99+
usage of passwords, and requires:
100+
101+
- An 8.6+ server, configured to use TLS with client certificates mapped - typically using the `CN` of the certificate as the user.
102+
- A matching `ACL` user account configured on the server, that is enabled (`on`) - i.e. the `ACL LIST` command should
103+
display something like `user MyUser2 on sanitize-payload ~* &* +@all` (the details will vary depending on the user permissions).
104+
- At the client: access to the client certificate pair.
105+
106+
For example:
107+
108+
``` csharp
109+
string certRoot = // some path to a folder with ca.crt, MyUser2.crt and MyUser2.key
110+
111+
var options = ConfigurationOptions.Parse("myserver:6380");
112+
options.SetUserPemCertificate(// automatically enables TLS
113+
userCertificatePath: Path.Combine(certRoot, "MyUser2.crt"),
114+
userKeyPath: Path.Combine(certRoot, "MyUser2.key"));
115+
options.TrustIssuer(Path.Combine(certRoot, "ca.crt"));
116+
await using var conn = await ConnectionMultiplexer.ConnectAsync(options);
117+
118+
// prove we are connected as MyUser2
119+
var user = (string?)await conn.GetDatabase().ExecuteAsync("acl", "whoami");
120+
Console.WriteLine(user); // writes "MyUser2"
121+
```
122+
123+
## More info
124+
125+
For more information:
126+
127+
- [Redis Security](https://redis.io/docs/latest/operate/oss_and_stack/management/security/)
128+
- [ACL](https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/)
129+
- [TLS](https://redis.io/docs/latest/operate/oss_and_stack/management/security/encryption/)

0 commit comments

Comments
 (0)