Skip to content
Open
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
94 changes: 94 additions & 0 deletions Security/src/AuthApi/LocalMutualTlsSupport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;

namespace Steeltoe.Samples.AuthApi;

internal static class LocalMutualTlsSupport
{
private const string ForwardedClientCertHeaderName = "X-Forwarded-Client-Cert";

// Mirrors LocalCertificateWriter's layout in Steeltoe.Common.Certificates: CA materials live one level
// up from the app directory, under GeneratedCertificates/trust, shared across samples in the solution.
private static readonly string TrustStorePath = ResolveTrustStorePath();
private static readonly X509Certificate2 RootCaCertificate = LoadTrustedCertificate("SteeltoeCA.crt");
private static readonly X509Certificate2 IntermediateCertificate = LoadTrustedCertificate("SteeltoeIntermediate.crt");

public static IServiceCollection AddLocalMutualTlsSupport(this IServiceCollection services)
{
services.PostConfigure<KestrelServerOptions>(options => options.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;

// Kestrel's default validation rejects Steeltoe certs because the Steeltoe-generated CA isn't OS-trusted.
// Validate against the Steeltoe CA/intermediate chain directly instead of trusting the OS store.
httpsOptions.ClientCertificateValidation = ValidateAgainstSteeltoeTrustChain;
}));

return services;
}

public static IApplicationBuilder UseLocalMutualTlsSupport(this IApplicationBuilder builder)
{
builder.Use(async (context, next) =>
{
context.Request.Headers.Remove(ForwardedClientCertHeaderName);

if (IsLoopbackAddress(context.Connection.RemoteIpAddress) && context.Connection.ClientCertificate != null)
{
context.Request.Headers[ForwardedClientCertHeaderName] = Convert.ToBase64String(context.Connection.ClientCertificate.RawData);
}

await next(context);
});

return builder;
}

private static bool IsLoopbackAddress(IPAddress? address)
{
if (address == null)
{
return false;
}

if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}

return IPAddress.IsLoopback(address);
}

private static bool ValidateAgainstSteeltoeTrustChain(X509Certificate2? certificate, X509Chain? remoteChain, SslPolicyErrors sslPolicyErrors)
{
if (certificate == null)
{
return false;
}

using var customChain = new X509Chain();
customChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
customChain.ChainPolicy.CustomTrustStore.Add(RootCaCertificate);
customChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

return customChain.Build(certificate);
}

private static string ResolveTrustStorePath()
{
string appBasePath = AppContext.BaseDirectory.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}", StringComparison.Ordinal)
? AppContext.BaseDirectory[..AppContext.BaseDirectory.LastIndexOf($"{Path.DirectorySeparatorChar}bin", StringComparison.Ordinal)]
: AppContext.BaseDirectory[..^1];

string parentPath = Directory.GetParent(appBasePath)?.ToString() ?? string.Empty;
return Path.Combine(parentPath, "GeneratedCertificates", "trust");
}

private static X509Certificate2 LoadTrustedCertificate(string fileName)
{
return X509Certificate2.CreateFromPem(File.ReadAllText(Path.Combine(TrustStorePath, fileName)));
}
}
14 changes: 13 additions & 1 deletion Security/src/AuthApi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
// Steeltoe: Add instance identity certificate to configuration.
builder.Configuration.AddAppInstanceIdentityCertificate(new Guid(orgId), new Guid(spaceId));

if (builder.Environment.IsDevelopment())
{
// Steeltoe: Simulate Gorouter's mTLS termination locally.
builder.Services.AddLocalMutualTlsSupport();
}

// Steeltoe: Register Microsoft's JWT Bearer and Certificate libraries for authentication, configure JWT to work with UAA/Cloud Foundry.
builder.Services.AddAuthentication().AddJwtBearer().ConfigureJwtBearerForCloudFoundry().AddCertificate();

Expand All @@ -35,13 +41,19 @@
policy.RequireClaim("scope", Globals.RequiredJwtScope);
})
// Steeltoe: Register policies requiring space or org to match between client and server certificates.
.AddOrgAndSpacePolicies();
.AddOrgAndSpacePoliciesForMutualTls();

// Steeltoe: Add actuator endpoints.
builder.Services.AddAllActuators();

WebApplication app = builder.Build();

if (app.Environment.IsDevelopment())
{
// Steeltoe: Simulate Gorouter's mTLS termination locally.
app.UseLocalMutualTlsSupport();
Comment thread
TimHess marked this conversation as resolved.
}

// Steeltoe: Use certificate and header forwarding along with ASP.NET Core Authentication and Authorization middleware.
app.UseCertificateAuthorization();

Expand Down
2 changes: 1 addition & 1 deletion Security/src/AuthConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
builder.Configuration.AddAppInstanceIdentityCertificate(new Guid(orgId), new Guid(spaceId));

// Steeltoe: register a typed HttpClient that includes the application instance identity certificate.
builder.Services.AddHttpClient<CertificateAuthorizationApiClient>(SetBaseAddress).AddAppInstanceIdentityCertificate().ConfigureLogging();
builder.Services.AddHttpClient<CertificateAuthorizationApiClient>(SetBaseAddress).AddAppInstanceIdentityCertificateForMutualTls().ConfigureLogging();

IHost host = builder.Build();
host.Run();
Expand Down
2 changes: 1 addition & 1 deletion Security/src/AuthWeb/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

// Steeltoe: Register HttpClients for communicating with a backend service, including an application instance certificate for authorization.
builder.Services.AddHttpClient<JwtAuthorizationApiClient>(SetBaseAddress).ConfigureLogging();
builder.Services.AddHttpClient<CertificateAuthorizationApiClient>(SetBaseAddress).AddAppInstanceIdentityCertificate().ConfigureLogging();
builder.Services.AddHttpClient<CertificateAuthorizationApiClient>(SetBaseAddress).AddAppInstanceIdentityCertificateForMutualTls().ConfigureLogging();

// Steeltoe: Add actuator endpoints.
builder.Services.AddAllActuators();
Expand Down