Skip to content

Commit d70ea30

Browse files
committed
fix(docs): align connection security guidance
Register and localize the compiled connection-security snippets, document the Entra package, and account for sample and package URLs which become available after publication. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e71c4ddd-5362-4204-910f-9a742ddd63de
1 parent edea7b5 commit d70ea30

7 files changed

Lines changed: 212 additions & 14 deletions

File tree

docs/Docs.slnx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@
6565
<Project Path="site/src/content/docs/host/snippets/aspire/SharedContracts/SharedContracts.csproj" />
6666
<Project Path="site/src/content/docs/host/snippets/aspire/Silo/Silo.csproj" />
6767
</Folder>
68+
<Folder Name="/site/src/content/docs/host/snippets/authenticated-silo-connections/" />
69+
<Folder Name="/site/src/content/docs/host/snippets/authenticated-silo-connections/csharp/">
70+
<Project Path="site/src/content/docs/host/snippets/authenticated-silo-connections/csharp/AuthenticatedSiloConnections.Snippets.csproj" />
71+
</Folder>
6872
<Folder Name="/site/src/content/docs/host/snippets/connection-middleware/">
6973
<Project Path="site/src/content/docs/host/snippets/connection-middleware/ConnectionMiddleware.csproj" />
7074
</Folder>

docs/site/src/content/docs/host/authenticated-silo-connections.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ Prefer an explicit <xref:Azure.Core.TokenCredential> appropriate to the hosting
164164
environment. The maintained sample supplies a `WorkloadIdentityCredential`; it
165165
doesn't silently use a developer or unrelated cached identity:
166166

167-
:::code language="csharp" source="../../../../../../samples/AuthenticatedSiloConnections/Program.cs" id="ExplicitCredential":::
167+
:::code language="csharp" source="snippets/authenticated-silo-connections/csharp/ConnectionAuthenticationExamples.cs" id="ExplicitCredential":::
168168

169169
Create the credential once and reuse it. The credential implementation owns its
170170
token cache.
@@ -178,7 +178,7 @@ public or private roots in the platform trust store and overlap old and new
178178
roots there during CA rotation. Each silo certificate therefore needs both the
179179
Server Authentication and Client Authentication EKUs.
180180

181-
:::code language="csharp" source="../../../../../../samples/AuthenticatedSiloConnections/SiloAuthentication.cs" id="AuthenticatedSiloConnections":::
181+
:::code language="csharp" source="snippets/authenticated-silo-connections/csharp/ConnectionAuthenticationExamples.cs" id="AuthenticatedSiloConnections":::
182182

183183
The configured `TargetHost` must match a DNS SAN and the chain must be valid
184184
and trusted. Never replace this policy with
@@ -203,11 +203,11 @@ token for outbound silo connections, so it needs both a provider and validator.
203203
Configure the gateway side on every silo. It validates client tokens before the
204204
gateway reads the Orleans connection preamble:
205205

206-
:::code language="csharp" source="../../../../../../samples/AuthenticatedSiloConnections/SiloAuthentication.cs" id="AuthenticatedClientGateway":::
206+
:::code language="csharp" source="snippets/authenticated-silo-connections/csharp/ConnectionAuthenticationExamples.cs" id="AuthenticatedClientGateway":::
207207

208208
Configure each external Orleans client with the corresponding outbound policy:
209209

210-
:::code language="csharp" source="../../../../../../samples/AuthenticatedSiloConnections/ClientAuthentication.cs" id="AuthenticatedClient":::
210+
:::code language="csharp" source="snippets/authenticated-silo-connections/csharp/ConnectionAuthenticationExamples.cs" id="AuthenticatedClient":::
211211

212212
The client and gateway must use compatible enforcement modes and the same Entra
213213
audience, tenant, cluster binding, client role, and caller authorization. Keep
@@ -330,7 +330,7 @@ hide a broken credential until token-expiry recycling or a process restart.
330330
Export the `Microsoft.Orleans.Connections.Security` meter. The maintained
331331
sample enables an OTLP exporter when `OTEL_EXPORTER_OTLP_ENDPOINT` is set:
332332

333-
:::code language="csharp" source="../../../../../../samples/AuthenticatedSiloConnections/Program.cs" id="FixedDiagnostics":::
333+
:::code language="csharp" source="snippets/authenticated-silo-connections/csharp/ConnectionAuthenticationExamples.cs" id="FixedDiagnostics":::
334334

335335
Alert on rates and latency for these instruments:
336336

docs/site/src/content/docs/host/snippets/authenticated-silo-connections/csharp/AuthenticatedSiloConnections.Snippets.csproj

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<OutputType>Exe</OutputType>
43
<TargetFramework>net10.0</TargetFramework>
54
<ImplicitUsings>enable</ImplicitUsings>
65
<Nullable>enable</Nullable>
7-
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
86
<RepositoryRoot>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\..\..\..\..\..\..\..\'))</RepositoryRoot>
97
</PropertyGroup>
108

11-
<ItemGroup>
12-
<Compile Include="$(RepositoryRoot)samples\AuthenticatedSiloConnections\Program.cs" Link="Program.cs" />
13-
<Compile Include="$(RepositoryRoot)samples\AuthenticatedSiloConnections\ClientAuthentication.cs" Link="ClientAuthentication.cs" />
14-
<Compile Include="$(RepositoryRoot)samples\AuthenticatedSiloConnections\SampleOptions.cs" Link="SampleOptions.cs" />
15-
<Compile Include="$(RepositoryRoot)samples\AuthenticatedSiloConnections\SiloAuthentication.cs" Link="SiloAuthentication.cs" />
16-
</ItemGroup>
17-
189
<ItemGroup>
1910
<ProjectReference Include="$(RepositoryRoot)src\Orleans.Server\Orleans.Server.csproj" />
2011
<ProjectReference Include="$(RepositoryRoot)src\Orleans.Connections.Security\Orleans.Connections.Security.csproj" />
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
using System.Security.Cryptography.X509Certificates;
2+
using Azure.Core;
3+
using Azure.Identity;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Hosting;
6+
using Microsoft.Extensions.Logging;
7+
using OpenTelemetry.Metrics;
8+
using OpenTelemetry.Resources;
9+
using Orleans.Connections.Security;
10+
using Orleans.Connections.Security.Entra;
11+
using Orleans.Hosting;
12+
13+
namespace Orleans.Docs.ConnectionSecurity;
14+
15+
internal static class ConnectionAuthenticationExamples
16+
{
17+
public static TokenCredential CreateCredential(ConnectionSecurityOptions options)
18+
{
19+
// <ExplicitCredential>
20+
TokenCredential credential = new WorkloadIdentityCredential(
21+
new WorkloadIdentityCredentialOptions
22+
{
23+
TenantId = options.Entra.TenantId,
24+
ClientId = options.Entra.WorkloadClientId,
25+
TokenFilePath = options.Entra.FederatedTokenFile,
26+
});
27+
// </ExplicitCredential>
28+
29+
return credential;
30+
}
31+
32+
public static void ConfigureSilo(
33+
ISiloBuilder siloBuilder,
34+
ConnectionSecurityOptions options,
35+
TokenCredential credential,
36+
X509Certificate2 siloCertificate)
37+
{
38+
// <AuthenticatedSiloConnections>
39+
siloBuilder.UseAuthenticatedSiloConnections(
40+
tls =>
41+
{
42+
tls.LocalCertificate = siloCertificate;
43+
tls.RemoteCertificateMode = RemoteCertificateMode.RequireCertificate;
44+
tls.ClientCertificateMode = RemoteCertificateMode.RequireCertificate;
45+
tls.CheckCertificateRevocation = true;
46+
},
47+
authentication =>
48+
{
49+
ConfigureAuthentication(
50+
authentication,
51+
options,
52+
credential,
53+
options.Entra.AllowedSiloCallerClientIds,
54+
"Orleans.Silo.Connect");
55+
});
56+
// </AuthenticatedSiloConnections>
57+
58+
// <AuthenticatedClientGateway>
59+
siloBuilder.UseAuthenticatedClientConnections(
60+
tls =>
61+
{
62+
tls.LocalCertificate = siloCertificate;
63+
tls.RemoteCertificateMode = RemoteCertificateMode.NoCertificate;
64+
},
65+
authentication =>
66+
{
67+
ConfigureAuthentication(
68+
authentication,
69+
options,
70+
credential,
71+
options.Entra.AllowedClientCallerClientIds,
72+
"Orleans.Client.Connect");
73+
});
74+
// </AuthenticatedClientGateway>
75+
}
76+
77+
public static void ConfigureClient(
78+
IClientBuilder clientBuilder,
79+
ConnectionSecurityOptions options,
80+
TokenCredential credential)
81+
{
82+
// <AuthenticatedClient>
83+
clientBuilder.UseAuthenticatedClientConnections(
84+
tls =>
85+
{
86+
tls.CheckCertificateRevocation = true;
87+
},
88+
authentication =>
89+
{
90+
ConfigureAuthentication(
91+
authentication,
92+
options,
93+
credential,
94+
options.Entra.AllowedClientCallerClientIds,
95+
"Orleans.Client.Connect");
96+
});
97+
// </AuthenticatedClient>
98+
}
99+
100+
public static void ConfigureDiagnostics(
101+
HostApplicationBuilder builder,
102+
bool exportToOtlp)
103+
{
104+
// <FixedDiagnostics>
105+
builder.Logging.ClearProviders();
106+
builder.Logging.AddJsonConsole(console =>
107+
{
108+
console.TimestampFormat = "O";
109+
console.JsonWriterOptions = new() { Indented = false };
110+
});
111+
builder.Logging.AddFilter("Orleans.Connections.Security", LogLevel.Information);
112+
builder.Logging.AddFilter("Azure.Identity", LogLevel.Warning);
113+
114+
builder.Services.AddOpenTelemetry()
115+
.ConfigureResource(resource => resource.AddService(
116+
serviceName: "authenticated-orleans-silo",
117+
serviceInstanceId: Environment.MachineName))
118+
.WithMetrics(metrics =>
119+
{
120+
metrics.AddMeter("Microsoft.Orleans.Connections.Security");
121+
122+
if (exportToOtlp)
123+
{
124+
metrics.AddOtlpExporter();
125+
}
126+
});
127+
// </FixedDiagnostics>
128+
}
129+
130+
private static void ConfigureAuthentication(
131+
SiloConnectionAuthenticationBuilder authentication,
132+
ConnectionSecurityOptions options,
133+
TokenCredential credential,
134+
IEnumerable<string> allowedCallerClientIds,
135+
string requiredRole)
136+
{
137+
authentication.Mode = options.AuthenticationMode;
138+
authentication.TargetHost = options.Certificate.TargetHost;
139+
authentication.TokenExchangeTimeout = TimeSpan.FromSeconds(10);
140+
authentication.MaxTokenSize = 16 * 1024;
141+
authentication.MaxConcurrentInboundAuthentications = 256;
142+
authentication.MaxConcurrentOutboundAuthentications = 256;
143+
authentication.MaxPendingInboundAuthentications = 256;
144+
authentication.MaxPendingOutboundAuthentications = 256;
145+
authentication.MinimumRemainingTokenLifetime = TimeSpan.FromMinutes(2);
146+
147+
authentication.UseEntra(
148+
credential,
149+
entra =>
150+
{
151+
entra.Authority = options.Entra.Authority;
152+
entra.TokenScope = $"{options.Entra.Audience}/.default";
153+
entra.ValidAudiences.Add(options.Entra.Audience);
154+
entra.ValidTenantIds.Add(options.Entra.TenantId);
155+
entra.ClusterAudienceFormat =
156+
$"api://{options.Entra.ResourceApplicationId}/{{0}}";
157+
158+
foreach (var clientId in allowedCallerClientIds)
159+
{
160+
entra.AllowedClientIds.Add(clientId);
161+
}
162+
163+
entra.RequiredRoles.Add(requiredRole);
164+
});
165+
}
166+
}
167+
168+
internal sealed class ConnectionSecurityOptions
169+
{
170+
public SiloConnectionAuthenticationMode AuthenticationMode { get; init; }
171+
172+
public CertificateOptions Certificate { get; init; } = new();
173+
174+
public EntraOptions Entra { get; init; } = new();
175+
}
176+
177+
internal sealed class CertificateOptions
178+
{
179+
public string TargetHost { get; init; } = "";
180+
}
181+
182+
internal sealed class EntraOptions
183+
{
184+
public string TenantId { get; init; } = "";
185+
186+
public string ResourceApplicationId { get; init; } = "";
187+
188+
public string WorkloadClientId { get; init; } = "";
189+
190+
public string FederatedTokenFile { get; init; } = "";
191+
192+
public string Audience { get; init; } = "";
193+
194+
public Uri Authority { get; init; } = null!;
195+
196+
public string[] AllowedSiloCallerClientIds { get; init; } = [];
197+
198+
public string[] AllowedClientCallerClientIds { get; init; } = [];
199+
}

docs/site/src/content/docs/resources/nuget-packages.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ For installation guidance, see [`dotnet package add`](https://learn.microsoft.co
2929
| [Microsoft.Orleans.Dashboard](https://www.nuget.org/packages/Microsoft.Orleans.Dashboard) | Built-in Orleans Dashboard server and UI. |
3030
| [Microsoft.Orleans.Dashboard.Abstractions](https://www.nuget.org/packages/Microsoft.Orleans.Dashboard.Abstractions) | Dashboard contracts for components which don't host the UI. |
3131
| [Microsoft.Orleans.Connections.Security](https://www.nuget.org/packages/Microsoft.Orleans.Connections.Security) | TLS support for Orleans connections. |
32+
| [Microsoft.Orleans.Connections.Security.Entra](https://www.nuget.org/packages/Microsoft.Orleans.Connections.Security.Entra) | Microsoft Entra workload authentication for Orleans connections. |
3233

3334
`Microsoft.Orleans.Runtime`, `Microsoft.Orleans.Core`, and the abstractions packages are lower-level dependencies of the metapackages. Reference them directly only when building a library with a narrower dependency requirement.
3435

docs/site/src/data/external-link-allowlist.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html": "AWS serves this public CLI configuration page to browsers but rejects the bounded automated HEAD/GET probe with HTTP 403.",
99
"https://docs.aws.amazon.com/sdk-for-net/v4/developer-guide/creds-assign.html": "AWS serves this public SDK credential-resolution page to browsers but rejects the bounded automated HEAD/GET probe with HTTP 403.",
1010
"https://docs.aws.amazon.com/streams/latest/dev/introduction.html": "AWS serves this public Kinesis overview to browsers but rejects the bounded automated HEAD/GET probe with HTTP 403.",
11+
"https://github.com/dotnet/orleans/tree/main/samples/AuthenticatedSiloConnections": "The sample is introduced by this pull request, so its permanent main-branch URL becomes available when the pull request merges.",
1112
"https://www.nuget.org/packages/Microsoft.Orleans.Clustering.Firestore": "The new Firestore clustering package is documented but not yet published; remove this entry and its unpublished API-package entry after publication.",
13+
"https://www.nuget.org/packages/Microsoft.Orleans.Connections.Security.Entra": "The new connection authentication package is documented but not yet published; remove this entry and its unpublished API-package entry after publication.",
1214
"https://www.nuget.org/packages/Microsoft.Orleans.GrainDirectory.Firestore": "The new Firestore grain-directory package is documented but not yet published; remove this entry and its unpublished API-package entry after publication.",
1315
"https://www.nuget.org/packages/Microsoft.Orleans.Journaling.Redis": "The new Redis journaling package is documented but not yet published; remove this entry and its unpublished API-package entry after publication.",
1416
"https://www.nuget.org/packages/Microsoft.Orleans.Persistence.Firestore": "The new Firestore persistence package is documented but not yet published; remove this entry and its unpublished API-package entry after publication.",

docs/site/src/data/unpublished-api-packages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"description": "Generated API assemblies which are not currently published as standalone NuGet packages.",
33
"packages": {
44
"Microsoft.Orleans.Clustering.Firestore": "The new provider is awaiting its first NuGet publication.",
5+
"Microsoft.Orleans.Connections.Security.Entra": "The new connection authentication provider is awaiting its first NuGet publication.",
56
"Microsoft.Orleans.GrainDirectory.Firestore": "The new provider is awaiting its first NuGet publication.",
67
"Microsoft.Orleans.Journaling.Redis": "The new provider is awaiting its first NuGet publication.",
78
"Microsoft.Orleans.Persistence.Firestore": "The new provider is awaiting its first NuGet publication.",

0 commit comments

Comments
 (0)