From 9bbeed045dab59ccfd77529b92bf7d6ae5c70cce Mon Sep 17 00:00:00 2001 From: Tim Hess Date: Mon, 3 Jun 2024 16:17:31 -0500 Subject: [PATCH] cleanup --- .../HostBuilderExtensionsTest.cs | 3 +- .../WebApplicationBuilderExtensionsTest.cs | 3 +- .../WebHostBuilderExtensionsTest.cs | 3 +- .../CertificateConfigurationExtensions.cs | 24 +++++----- .../CertificateServiceCollectionExtensions.cs | 5 ++- .../Common.Security/LocalCertificateWriter.cs | 38 ++++++++-------- .../ConfigureCertificateOptionsTest.cs | 4 +- .../LocalCertificateWriterTest.cs | 3 +- .../CloudFoundryPostProcessor.cs | 4 +- .../IdentityCloudFoundryPostProcessor.cs | 12 +++-- .../JwtBearerServiceCollectionExtensions.cs | 16 ++++--- .../PostConfigureJwtBearerOptions.cs | 11 +++-- ...e.Security.Authentication.JwtBearer.csproj | 2 +- ...penIdConnectServiceCollectionExtensions.cs | 18 +++++--- .../PostConfigureOpenIdConnectOptions.cs | 44 +++++++++---------- ...curity.Authentication.OpenIdConnect.csproj | 2 +- .../SharedServiceCollectionExtensions.cs | 1 + .../Authentication.Shared/TokenKeyResolver.cs | 9 ++-- .../ApplicationInstanceCertificate.cs | 23 +++++----- ...CertificateApplicationBuilderExtensions.cs | 4 +- .../CertificateAuthorizationDefaults.cs | 2 +- .../CertificateAuthorizationHandler.cs | 12 ++--- .../CertificateServiceCollectionExtensions.cs | 22 ++++++---- .../Authorization.Certificate/GlobalUsings.cs | 2 + ...nfigureCertificateAuthenticationOptions.cs | 39 +++++++++------- .../PublicAPI.Unshipped.txt | 2 +- ...wtBearerServiceCollectionExtensionsTest.cs | 5 ++- .../PostConfigureJwtBearerOptionsTest.cs | 14 +++--- ...dConnectServiceCollectionExtensionsTest.cs | 5 ++- .../PostConfigureOpenIdConnectOptionsTest.cs | 23 +++------- .../CertificateAuthorizationTest.cs | 19 ++++---- .../TestServerCertificateStartup.cs | 20 +++------ src/Steeltoe.Connectors.slnf | 2 - src/Steeltoe.Security.slnf | 13 +++--- 34 files changed, 219 insertions(+), 190 deletions(-) diff --git a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs index 26f77170da..2c05338bb0 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/HostBuilderExtensionsTest.cs @@ -4,7 +4,6 @@ using System.Reflection; using FluentAssertions; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -227,7 +226,7 @@ public void Tracing_IsAutowired() } [Fact] - public void CloudFoundryContainerSecurity_IsAutowired() + public void ContainerIdentityCertificate_IsAutowired() { using IHost host = GetHostForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs index 7348ed8a12..9de3996801 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebApplicationBuilderExtensionsTest.cs @@ -5,7 +5,6 @@ using System.Net; using System.Reflection; using FluentAssertions; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -228,7 +227,7 @@ public void Tracing_IsAutowired() } [Fact] - public void CloudFoundryContainerSecurity_IsAutowired() + public void ContainerIdentityCertificate_IsAutowired() { using WebApplication host = GetWebApplicationForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); diff --git a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs index 6ac47c1b87..7035ee4cef 100644 --- a/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs +++ b/src/Bootstrap/test/AutoConfiguration.Test/WebHostBuilderExtensionsTest.cs @@ -5,7 +5,6 @@ using System.Reflection; using FluentAssertions; using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; @@ -228,7 +227,7 @@ public void Tracing_IsAutowired() } [Fact] - public void CloudFoundryContainerSecurity_IsAutowired() + public void ContainerIdentityCertificate_IsAutowired() { using IWebHost host = GetWebHostForOnly(SteeltoeAssemblyNames.CommonSecurity); var configuration = host.Services.GetRequiredService(); diff --git a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs index 69abcb3982..cd5cd9f7de 100644 --- a/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs +++ b/src/Common/src/Common.Security/CertificateConfigurationExtensions.cs @@ -49,15 +49,15 @@ public static IConfigurationBuilder AddCertificate(this IConfigurationBuilder bu } /// - /// Adds PEM files representing application identity to application configuration. When running outside of Cloud Foundry based platforms, - /// will create certificates resembling those found on the platform. + /// Adds PEM files representing application identity to application configuration. When running outside of Cloud Foundry based platforms, will create + /// certificates resembling those found on the platform. /// /// /// Your . /// /// - /// Outside of Cloud Foundry, CA and Intermediate certificates will be created a directory above the current project so that they can - /// be shared between different projects operating in the same solution context. + /// Outside of Cloud Foundry, CA and Intermediate certificates will be created a directory above the current project so that they can be shared between + /// different projects operating in the same solution context. /// public static IConfigurationBuilder AddContainerIdentityCertificate(this IConfigurationBuilder builder) { @@ -65,34 +65,34 @@ public static IConfigurationBuilder AddContainerIdentityCertificate(this IConfig } /// - /// Adds PEM files representing application identity to application configuration. When running outside of Cloud Foundry based platforms, - /// will create certificates resembling those found on the platform. + /// Adds PEM files representing application identity to application configuration. When running outside of Cloud Foundry based platforms, will create + /// certificates resembling those found on the platform. /// /// /// Your . /// /// - /// (Optional) A GUID representing an organization, for use with Cloud Foundry certificate-based authorization - /// policy. + /// (Optional) A GUID representing an organization, for use with Cloud Foundry certificate-based authorization policy. /// /// /// (Optional) A GUID representing a space, for use with Cloud Foundry certificate-based authorization policy. /// /// - /// Outside of Cloud Foundry, CA and Intermediate certificates will be created a directory above the current project so that they can - /// be shared between different projects operating in the same solution context. + /// Outside of Cloud Foundry, CA and Intermediate certificates will be created a directory above the current project so that they can be shared between + /// different projects operating in the same solution context. /// public static IConfigurationBuilder AddContainerIdentityCertificate(this IConfigurationBuilder builder, Guid? organizationId, Guid? spaceId) { if (!Platform.IsCloudFoundry) { organizationId ??= Guid.NewGuid(); - spaceId ??= Guid.NewGuid(); + spaceId ??= Guid.NewGuid(); var task = new LocalCertificateWriter(); task.Write((Guid)organizationId, (Guid)spaceId); - Environment.SetEnvironmentVariable("CF_SYSTEM_CERT_PATH", Path.Combine(Directory.GetParent(LocalCertificateWriter.ApplicationBasePath)!.ToString(), "GeneratedCertificates")); + Environment.SetEnvironmentVariable("CF_SYSTEM_CERT_PATH", + Path.Combine(Directory.GetParent(LocalCertificateWriter.ApplicationBasePath)!.ToString(), "GeneratedCertificates")); Environment.SetEnvironmentVariable("CF_INSTANCE_CERT", Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem")); diff --git a/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs b/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs index 7004c5fbdc..73734a60dd 100644 --- a/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs +++ b/src/Common/src/Common.Security/CertificateServiceCollectionExtensions.cs @@ -14,7 +14,7 @@ namespace Steeltoe.Common.Security; public static class CertificateServiceCollectionExtensions { /// - /// Configure for use with client certificates. + /// Configure for use with client certificates. /// /// /// The to add services to. @@ -25,7 +25,8 @@ public static class CertificateServiceCollectionExtensions /// /// Provides access to the file system. /// - public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, IConfiguration configuration, IFileProvider? fileProvider = null) + public static IServiceCollection ConfigureCertificateOptions(this IServiceCollection services, IConfiguration configuration, + IFileProvider? fileProvider = null) { fileProvider ??= new PhysicalFileProvider(Environment.CurrentDirectory); IConfigurationSection[] childSections = configuration.GetSection(CertificateOptions.ConfigurationKeyPrefix).GetChildren().ToArray(); diff --git a/src/Common/src/Common.Security/LocalCertificateWriter.cs b/src/Common/src/Common.Security/LocalCertificateWriter.cs index 4f7603b818..f75699ce3e 100644 --- a/src/Common/src/Common.Security/LocalCertificateWriter.cs +++ b/src/Common/src/Common.Security/LocalCertificateWriter.cs @@ -16,7 +16,8 @@ internal sealed class LocalCertificateWriter internal string CertificateFilenamePrefix { get; set; } = "SteeltoeInstance"; - public string RootCertificateAuthorityPfxPath { get; set; } = Path.Combine(Directory.GetParent(ApplicationBasePath)!.ToString(), "GeneratedCertificates", "SteeltoeCA.pfx"); + public string RootCertificateAuthorityPfxPath { get; set; } = + Path.Combine(Directory.GetParent(ApplicationBasePath)!.ToString(), "GeneratedCertificates", "SteeltoeCA.pfx"); public string IntermediatePfxPath { get; set; } = Path.Combine(Directory.GetParent(ApplicationBasePath)!.ToString(), "GeneratedCertificates", "SteeltoeIntermediate.pfx"); @@ -52,6 +53,7 @@ public void Write(Guid orgId, Guid spaceId) // Create the intermediate certificate if it doesn't already exist (can be shared by multiple applications) X509Certificate2 intermediateCertificate; + if (!File.Exists(IntermediatePfxPath)) { intermediateCertificate = CreateIntermediateCertificate("CN=SteeltoeGeneratedIntermediate", rootAuthorityCertificate); @@ -73,23 +75,15 @@ public void Write(Guid orgId, Guid spaceId) } #if NET8_0_OR_GREATER - string chainedCertificateContents = clientCertificate.ExportCertificatePem() + "\r\n" + - intermediateCertificate.ExportCertificatePem() + "\r\n" + + string chainedCertificateContents = clientCertificate.ExportCertificatePem() + "\r\n" + intermediateCertificate.ExportCertificatePem() + "\r\n" + rootAuthorityCertificate.ExportCertificatePem(); + string keyContents = clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem(); #else - string chainedCertificateContents = "-----BEGIN CERTIFICATE-----\r\n" + - Convert.ToBase64String(clientCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + - "\r\n-----END CERTIFICATE-----\r\n" + "-----BEGIN CERTIFICATE-----\r\n" + - Convert.ToBase64String(intermediateCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + - "\r\n-----END CERTIFICATE-----\r\n" + "-----BEGIN CERTIFICATE-----\r\n" + - Convert.ToBase64String(rootAuthorityCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + - "\r\n-----END CERTIFICATE-----\r\n"; - - string keyContents = "-----BEGIN RSA PRIVATE KEY-----\r\n" + - Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + - "\r\n-----END RSA PRIVATE KEY-----"; + string chainedCertificateContents = "-----BEGIN CERTIFICATE-----\r\n" + Convert.ToBase64String(clientCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END CERTIFICATE-----\r\n" + "-----BEGIN CERTIFICATE-----\r\n" + Convert.ToBase64String(intermediateCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END CERTIFICATE-----\r\n" + "-----BEGIN CERTIFICATE-----\r\n" + Convert.ToBase64String(rootAuthorityCertificate.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END CERTIFICATE-----\r\n"; + + string keyContents = "-----BEGIN RSA PRIVATE KEY-----\r\n" + Convert.ToBase64String(clientCertificate.GetRSAPrivateKey()!.ExportRSAPrivateKey(), Base64FormattingOptions.InsertLineBreaks) + "\r\n-----END RSA PRIVATE KEY-----"; #endif @@ -100,7 +94,9 @@ public void Write(Guid orgId, Guid spaceId) private static X509Certificate2 CreateRootCertificate(string distinguishedName) { using var privateKey = RSA.Create(); - var certificateRequest = new CertificateRequest(new X500DistinguishedName(distinguishedName), privateKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + var certificateRequest = + new CertificateRequest(new X500DistinguishedName(distinguishedName), privateKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); certificateRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true)); @@ -120,17 +116,18 @@ private static X509Certificate2 CreateIntermediateCertificate(string subjectName return certificateRequest.Create(issuerCertificate, DateTimeOffset.UtcNow, issuerCertificate.NotAfter, serialNumber).CopyWithPrivateKey(privateKey); } - private static X509Certificate2 CreateClientCertificate(string subjectName, X509Certificate2 issuerCertificate, SubjectAlternativeNameBuilder alternativeNames, DateTimeOffset? notAfter = null) + private static X509Certificate2 CreateClientCertificate(string subjectName, X509Certificate2 issuerCertificate, + SubjectAlternativeNameBuilder alternativeNames, DateTimeOffset? notAfter = null) { using var privateKey = RSA.Create(); var request = new CertificateRequest(subjectName, privateKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false)); request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false)); - request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension( - [ + + request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension([ new Oid("1.3.6.1.5.5.7.3.1"), // serverAuth - new Oid("1.3.6.1.5.5.7.3.2") // clientAuth + new Oid("1.3.6.1.5.5.7.3.2") // clientAuth ], false)); if (alternativeNames != null) @@ -145,7 +142,8 @@ private static X509Certificate2 CreateClientCertificate(string subjectName, X509 randomNumberGenerator.GetBytes(serialNumber); } - X509Certificate2 signedCertificate = request.Create(issuerCertificate, DateTimeOffset.UtcNow, notAfter ?? DateTimeOffset.UtcNow.AddDays(1), serialNumber); + X509Certificate2 signedCertificate = + request.Create(issuerCertificate, DateTimeOffset.UtcNow, notAfter ?? DateTimeOffset.UtcNow.AddDays(1), serialNumber); return signedCertificate.CopyWithPrivateKey(privateKey); } diff --git a/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs b/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs index df3dd957a8..7027045f17 100644 --- a/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs +++ b/src/Common/test/Common.Security.Test/ConfigureCertificateOptionsTest.cs @@ -85,7 +85,7 @@ public async Task CertificateOptionsUpdateOnFileChange() IConfigurationRoot configuration = new ConfigurationBuilder().AddCertificate(CertificateName, certificateFilePath, privateKeyFilePath).Build(); - ServiceProvider serviceProvider = new ServiceCollection().AddSingleton(configuration).ConfigureCertificateOptions(configuration, null) + ServiceProvider serviceProvider = new ServiceCollection().AddSingleton(configuration).ConfigureCertificateOptions(configuration) .BuildServiceProvider(); var optionsMonitor = serviceProvider.GetRequiredService>(); @@ -116,7 +116,7 @@ public async Task CertificateOptionsNotifyOnChange() IConfigurationRoot configuration = new ConfigurationBuilder().AddCertificate(CertificateName, certificateFilePath, privateKeyFilePath).Build(); - ServiceProvider serviceProvider = new ServiceCollection().AddSingleton(configuration).ConfigureCertificateOptions(configuration, null) + ServiceProvider serviceProvider = new ServiceCollection().AddSingleton(configuration).ConfigureCertificateOptions(configuration) .BuildServiceProvider(); var optionsMonitor = serviceProvider.GetRequiredService>(); diff --git a/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs b/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs index 398c248480..0daae12521 100644 --- a/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs +++ b/src/Common/test/Common.Security.Test/LocalCertificateWriterTest.cs @@ -26,7 +26,8 @@ public void CertificatesIncludeParams() rsa.ImportFromPem(File.ReadAllText(Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceKey.pem"))); X509Certificate2 clientCert = - new X509Certificate2(File.ReadAllBytes(Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"))) + new X509Certificate2( + File.ReadAllBytes(Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", "SteeltoeInstanceCert.pem"))) .CopyWithPrivateKey(rsa); rootCertificate.Should().NotBeNull(); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs index 1e7b2e3e5e..15883ad064 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/CloudFoundryPostProcessor.cs @@ -9,8 +9,8 @@ namespace Steeltoe.Configuration.CloudFoundry.ServiceBinding.PostProcessors; internal abstract class CloudFoundryPostProcessor : IConfigurationPostProcessor { - private static readonly Regex TagsConfigurationKeyRegex = new("^vcap:services:[^:]+:[0-9]+:tags:[0-9]+", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex LabelConfigurationKeyRegex = new("^vcap:services:[^:]+:[0-9]+:label+", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex TagsConfigurationKeyRegex = new("^vcap:services:[^:]+:[0-9]+:tags:[0-9]+", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); + private static readonly Regex LabelConfigurationKeyRegex = new("^vcap:services:[^:]+:[0-9]+:label+", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1)); public abstract void PostProcessConfiguration(PostProcessorConfigurationProvider provider, IDictionary configurationData); diff --git a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs index cdb384648a..815437fc46 100644 --- a/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs +++ b/src/Configuration/src/CloudFoundry.ServiceBinding/PostProcessors/IdentityCloudFoundryPostProcessor.cs @@ -11,7 +11,13 @@ internal sealed class IdentityCloudFoundryPostProcessor : CloudFoundryPostProces { internal const string BindingType = "p-identity"; internal const string AuthenticationConfigurationKeyPrefix = "Authentication:Schemes"; - internal static readonly string[] AuthenticationSchemes = ["OpenIdConnect", "Bearer"]; + + internal static readonly string[] AuthenticationSchemes = + [ + "OpenIdConnect", + "Bearer" + ]; + private readonly ILogger _logger; public IdentityCloudFoundryPostProcessor(ILogger logger) @@ -34,14 +40,14 @@ public override void PostProcessConfiguration(PostProcessorConfigurationProvider } var mapper = ServiceBindingMapper.Create(configurationData, key, BindingType, AuthenticationConfigurationKeyPrefix); - foreach (var scheme in AuthenticationSchemes) + + foreach (string scheme in AuthenticationSchemes) { mapper.MapFromTo("credentials:auth_domain", $"{scheme}:Authority"); mapper.MapFromTo("credentials:client_id", $"{scheme}:ClientId"); mapper.MapFromTo("credentials:client_secret", $"{scheme}:ClientSecret"); } - hasMapped = true; } } diff --git a/src/Security/src/Authentication.Jwt/JwtBearerServiceCollectionExtensions.cs b/src/Security/src/Authentication.Jwt/JwtBearerServiceCollectionExtensions.cs index 4007acba9a..80803a7fda 100644 --- a/src/Security/src/Authentication.Jwt/JwtBearerServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.Jwt/JwtBearerServiceCollectionExtensions.cs @@ -13,19 +13,25 @@ namespace Steeltoe.Security.Authentication.JwtBearer; public static class JwtBearerServiceCollectionExtensions { /// - /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. + /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. /// - /// The to add services to. + /// + /// The to add services to. + /// public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IServiceCollection services) { return ConfigureJwtBearerForCloudFoundry(services, null); } /// - /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. + /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Platform. /// - /// The to add services to. - /// Configure the HttpClient used to interact with the identity server. + /// + /// The to add services to. + /// + /// + /// Configure the HttpClient used to interact with the identity server. + /// public static IServiceCollection ConfigureJwtBearerForCloudFoundry(this IServiceCollection services, Action? configureHttpClient) { ArgumentGuard.NotNull(services); diff --git a/src/Security/src/Authentication.Jwt/PostConfigureJwtBearerOptions.cs b/src/Security/src/Authentication.Jwt/PostConfigureJwtBearerOptions.cs index 18898ca8c1..9f42c0fd13 100644 --- a/src/Security/src/Authentication.Jwt/PostConfigureJwtBearerOptions.cs +++ b/src/Security/src/Authentication.Jwt/PostConfigureJwtBearerOptions.cs @@ -9,17 +9,22 @@ namespace Steeltoe.Security.Authentication.JwtBearer; -internal sealed class PostConfigureJwtBearerOptions(IConfiguration configuration, IHttpClientFactory httpClientFactory) : IPostConfigureOptions +internal sealed class PostConfigureJwtBearerOptions(IConfiguration configuration, IHttpClientFactory httpClientFactory) + : IPostConfigureOptions { private const string BearerConfigurationKeyPrefix = "Authentication:Schemes:Bearer"; public void PostConfigure(string? name, JwtBearerOptions options) { - var clientId = configuration.GetValue($"{BearerConfigurationKeyPrefix}:ClientId"); + string? clientId = configuration.GetValue($"{BearerConfigurationKeyPrefix}:ClientId"); if (!string.IsNullOrEmpty(clientId) && options.TokenValidationParameters.ValidAudiences?.Contains(clientId) != true) { - var audiences = new List(options.TokenValidationParameters.ValidAudiences ?? []) { clientId }; + var audiences = new List(options.TokenValidationParameters.ValidAudiences ?? []) + { + clientId + }; + options.TokenValidationParameters.ValidAudiences = audiences; } diff --git a/src/Security/src/Authentication.Jwt/Steeltoe.Security.Authentication.JwtBearer.csproj b/src/Security/src/Authentication.Jwt/Steeltoe.Security.Authentication.JwtBearer.csproj index a23676d980..73dd2f0faa 100644 --- a/src/Security/src/Authentication.Jwt/Steeltoe.Security.Authentication.JwtBearer.csproj +++ b/src/Security/src/Authentication.Jwt/Steeltoe.Security.Authentication.JwtBearer.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs index cdfceec619..6d59de088d 100644 --- a/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/OpenIdConnectServiceCollectionExtensions.cs @@ -13,19 +13,27 @@ namespace Steeltoe.Security.Authentication.OpenIdConnect; public static class OpenIdConnectServiceCollectionExtensions { /// - /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Application Service. + /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Application + /// Service. /// - /// The to add services to. + /// + /// The to add services to. + /// public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this IServiceCollection services) { return ConfigureOpenIdConnectForCloudFoundry(services, null); } /// - /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Application Service. + /// Configure for compatibility with UAA-based systems, including Single Sign-On for VMware Tanzu Application + /// Service. /// - /// The to add services to. - /// Configure the HttpClient used to interact with the identity server. + /// + /// The to add services to. + /// + /// + /// Configure the HttpClient used to interact with the identity server. + /// public static IServiceCollection ConfigureOpenIdConnectForCloudFoundry(this IServiceCollection services, Action? configureHttpClient) { ArgumentGuard.NotNull(services); diff --git a/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs b/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs index b5aa481b01..a8e1d5bf0f 100644 --- a/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs +++ b/src/Security/src/Authentication.OpenIdConnect/PostConfigureOpenIdConnectOptions.cs @@ -12,11 +12,28 @@ namespace Steeltoe.Security.Authentication.OpenIdConnect; -// ILogger logger -/// internal sealed class PostConfigureOpenIdConnectOptions(IHttpClientFactory httpClientFactory) : IPostConfigureOptions { - /// + // The ClaimsIdentity is built off the id_token, but scopes are returned in the access_token. + // Identify scopes not already present as claims and add them to the ClaimsIdentity + private static readonly Func MapScopesToClaims = tokenValidatedContext => + { + if (tokenValidatedContext.Principal?.Identity is not ClaimsIdentity claimsIdentity) + { + return Task.FromResult(1); + } + + string scopes = tokenValidatedContext.TokenEndpointResponse?.Scope ?? string.Empty; + + IEnumerable claimsFromScopes = scopes.Split(' ') + .Where(scope => !claimsIdentity.Claims.Any(claim => claim.Type == "scope" && claim.Value == scope)) + .Select(claimValue => new Claim("scope", claimValue)); + + claimsIdentity.AddClaims(claimsFromScopes); + + return Task.FromResult(0); + }; + public void PostConfigure(string? name, OpenIdConnectOptions options) { ArgumentNullException.ThrowIfNull(name); @@ -45,25 +62,4 @@ public void PostConfigure(string? name, OpenIdConnectOptions options) options.TokenValidationParameters.IssuerSigningKeyResolver = keyResolver.ResolveSigningKey; } } - - // The ClaimsIdentity is built off the id_token, but scopes are returned in the access_token. - // Identify scopes not already present as claims and add them to the ClaimsIdentity - private static readonly Func MapScopesToClaims = tokenValidatedContext => - { - if (tokenValidatedContext.Principal?.Identity is not ClaimsIdentity claimsIdentity) - { - return Task.FromResult(1); - } - - string scopes = tokenValidatedContext.TokenEndpointResponse?.Scope ?? string.Empty; - - IEnumerable claimsFromScopes = scopes - .Split(' ') - .Where(scope => !claimsIdentity.Claims.Any(claim => claim.Type == "scope" && claim.Value == scope)) - .Select(claimValue => new Claim("scope", claimValue)); - - claimsIdentity.AddClaims(claimsFromScopes); - - return Task.FromResult(0); - }; } diff --git a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj index f78d59263b..1f8f7e3b04 100644 --- a/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj +++ b/src/Security/src/Authentication.OpenIdConnect/Steeltoe.Security.Authentication.OpenIdConnect.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs b/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs index b425da0e6c..b0edd671ff 100644 --- a/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs +++ b/src/Security/src/Authentication.Shared/SharedServiceCollectionExtensions.cs @@ -15,6 +15,7 @@ internal static IServiceCollection AddSteeltoeSecurityHttpClient(this IServiceCo client.Timeout = TimeSpan.FromSeconds(60); configureHttpClient?.Invoke(client); }); + return services; } } diff --git a/src/Security/src/Authentication.Shared/TokenKeyResolver.cs b/src/Security/src/Authentication.Shared/TokenKeyResolver.cs index 3a9b9ce9df..70c0f7fde3 100644 --- a/src/Security/src/Authentication.Shared/TokenKeyResolver.cs +++ b/src/Security/src/Authentication.Shared/TokenKeyResolver.cs @@ -11,8 +11,10 @@ namespace Steeltoe.Security.Authentication.Shared; internal sealed class TokenKeyResolver { - private string _authority; private readonly HttpClient _httpClient; + private string _authority; + + internal static ConcurrentDictionary Resolved { get; } = new(); public TokenKeyResolver(string authority, HttpClient httpClient) { @@ -23,10 +25,7 @@ public TokenKeyResolver(string authority, HttpClient httpClient) _httpClient = httpClient; } - internal static ConcurrentDictionary Resolved { get; } = new(); - - internal IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string kid, - TokenValidationParameters validationParameters) + internal IEnumerable ResolveSigningKey(string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) { if (Resolved.TryGetValue(kid, out SecurityKey? resolved)) { diff --git a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs index 6786a5a28a..aabeb95355 100644 --- a/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs +++ b/src/Security/src/Authorization.Certificate/ApplicationInstanceCertificate.cs @@ -17,14 +17,6 @@ internal sealed class ApplicationInstanceCertificate private const string SteeltoeInstanceCertificateSubjectRegex = @"^CN=(?[0-9a-f-]+),\sOU=app:(?[0-9a-f-]+)\s\+\sOU=space:(?[0-9a-f-]+)\s\+\sOU=organization:(?[0-9a-f-]+)$"; - private ApplicationInstanceCertificate(string organizationId, string spaceId, string applicationId, string instanceId) - { - OrganizationId = organizationId; - SpaceId = spaceId; - ApplicationId = applicationId; - InstanceId = instanceId; - } - public string OrganizationId { get; private set; } public string SpaceId { get; private set; } @@ -33,11 +25,20 @@ private ApplicationInstanceCertificate(string organizationId, string spaceId, st public string InstanceId { get; private set; } + private ApplicationInstanceCertificate(string organizationId, string spaceId, string applicationId, string instanceId) + { + OrganizationId = organizationId; + SpaceId = spaceId; + ApplicationId = applicationId; + InstanceId = instanceId; + } + public static bool TryParse(X509Certificate2 certificate, [MaybeNullWhen(false)] out ApplicationInstanceCertificate outInstanceCertificate, ILogger logger) { outInstanceCertificate = null; - Match instanceMatch = Regex.Match(certificate.Subject.Replace("\"", string.Empty, StringComparison.Ordinal), CloudFoundryInstanceCertificateSubjectRegex); + Match instanceMatch = Regex.Match(certificate.Subject.Replace("\"", string.Empty, StringComparison.Ordinal), + CloudFoundryInstanceCertificateSubjectRegex); if (!instanceMatch.Success) { @@ -46,7 +47,9 @@ public static bool TryParse(X509Certificate2 certificate, [MaybeNullWhen(false)] if (instanceMatch.Success) { - outInstanceCertificate = new ApplicationInstanceCertificate(instanceMatch.Groups["org"].Value,instanceMatch.Groups["space"].Value, instanceMatch.Groups["app"].Value, instanceMatch.Groups["instance"].Value); + outInstanceCertificate = new ApplicationInstanceCertificate(instanceMatch.Groups["org"].Value, instanceMatch.Groups["space"].Value, + instanceMatch.Groups["app"].Value, instanceMatch.Groups["instance"].Value); + logger.LogTrace("Successfully parsed an identity certificate with subject {CertificateSubject}", certificate.Subject); } else diff --git a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs index a0a3364418..5f9a62d2ad 100644 --- a/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateApplicationBuilderExtensions.cs @@ -11,8 +11,8 @@ namespace Steeltoe.Security.Authorization.Certificate; public static class CertificateApplicationBuilderExtensions { /// - /// Enable certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares. - /// Sets ForwardedHeaders to . + /// Enable certificate and header forwarding, along with ASP.NET Core authentication and authorization middlewares. Sets ForwardedHeaders to + /// . /// /// /// The . diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs index a5ba5a395e..9224b83ee5 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationDefaults.cs @@ -8,6 +8,6 @@ public static class CertificateAuthorizationDefaults { public const string SameOrganizationAuthorizationPolicy = "sameorg"; public const string SameSpaceAuthorizationPolicy = "samespace"; - public const string ClientCertificateHeader = "X-Client-Cert"; // "somethingcompletelyunexpected" + public const string ClientCertificateHeader = "X-Client-Cert"; public const string HttpClientName = "IncludeClientCertificate"; } diff --git a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs index a6d6ed2fbb..73cd9136bf 100644 --- a/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs +++ b/src/Security/src/Authorization.Certificate/CertificateAuthorizationHandler.cs @@ -3,9 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Security.Claims; -using Microsoft.Extensions.Options; using Steeltoe.Common; -using Steeltoe.Common.Options; namespace Steeltoe.Security.Authorization.Certificate; @@ -25,7 +23,9 @@ public CertificateAuthorizationHandler(IOptionsMonitor certi public Task HandleAsync(AuthorizationHandlerContext context) { - HandleCertificateAuthorizationRequirement(context, ApplicationClaimTypes.OrganizationId, _applicationInstanceCertificate?.OrganizationId); + HandleCertificateAuthorizationRequirement(context, ApplicationClaimTypes.OrganizationId, + _applicationInstanceCertificate?.OrganizationId); + HandleCertificateAuthorizationRequirement(context, ApplicationClaimTypes.SpaceId, _applicationInstanceCertificate?.SpaceId); return Task.CompletedTask; } @@ -37,7 +37,8 @@ private void OnCertificateRefresh(CertificateOptions certificateOptions) return; } - if (ApplicationInstanceCertificate.TryParse(certificateOptions.Certificate, out ApplicationInstanceCertificate? applicationInstanceCertificate, _logger)) + if (ApplicationInstanceCertificate.TryParse(certificateOptions.Certificate, out ApplicationInstanceCertificate? applicationInstanceCertificate, + _logger)) { _applicationInstanceCertificate = applicationInstanceCertificate; } @@ -65,7 +66,8 @@ private void HandleCertificateAuthorizationRequirement(AuthorizationHandlerCo } else { - _logger.LogDebug("User has the required claim, but the value doesn't match. Expected {ClaimValue} but got {ClaimType}", claimValue, context.User.FindFirstValue((claimType))); + _logger.LogDebug("User has the required claim, but the value doesn't match. Expected {ClaimValue} but got {ClaimType}", claimValue, + context.User.FindFirstValue(claimType)); } } } diff --git a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs index 52c5a1fdeb..2f0b9a2144 100644 --- a/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs +++ b/src/Security/src/Authorization.Certificate/CertificateServiceCollectionExtensions.cs @@ -7,8 +7,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; -using Steeltoe.Common.Options; using Steeltoe.Common.Security; namespace Steeltoe.Security.Authorization.Certificate; @@ -27,9 +25,11 @@ public static class CertificateServiceCollectionExtensions public static IServiceCollection AddCertificateAuthorizationServer(this IServiceCollection services, IConfiguration configuration) { services.ConfigureCertificateOptions(configuration); + services.AddCertificateForwarding(opt => { opt.CertificateHeader = CertificateAuthorizationDefaults.ClientCertificateHeader; + opt.HeaderConverter = certificateData => { var certificate = new X509Certificate2(Encoding.ASCII.GetBytes(certificateData)); @@ -37,13 +37,15 @@ public static IServiceCollection AddCertificateAuthorizationServer(this IService }; }); - services.TryAddEnumerable(ServiceDescriptor.Singleton, PostConfigureCertificateAuthenticationOptions>()); + services.TryAddEnumerable(ServiceDescriptor + .Singleton, PostConfigureCertificateAuthenticationOptions>()); + services.AddSingleton(); return services; } /// - /// Add a named and necessary components for finding client certificates and attaching to outbound requests. + /// Add a named and necessary components for finding client certificates and attaching to outbound requests. /// /// /// The to add services to. @@ -55,16 +57,20 @@ public static IServiceCollection AddCertificateAuthorizationClient(this IService { services.ConfigureCertificateOptions(configuration); services.TryAddSingleton, PostConfigureCertificateAuthenticationOptions>(); + services.AddHttpClient(CertificateAuthorizationDefaults.HttpClientName, (serviceProvider, client) => { var loggerFactory = serviceProvider.GetRequiredService(); - var logger = loggerFactory.CreateLogger(("Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions")); + ILogger logger = loggerFactory.CreateLogger("Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions"); var optionsMonitor = serviceProvider.GetService>(); - var certificateOptions = optionsMonitor?.Get("ContainerIdentity"); - var certificate = certificateOptions?.Certificate; + CertificateOptions? certificateOptions = optionsMonitor?.Get("ContainerIdentity"); + X509Certificate2? certificate = certificateOptions?.Certificate; + if (certificate != null) { - logger.LogDebug("Adding certificate with subject {CertificateSubject} to outbound requests in header {Header}", certificate.Subject, CertificateAuthorizationDefaults.ClientCertificateHeader); + logger.LogDebug("Adding certificate with subject {CertificateSubject} to outbound requests in header {Header}", certificate.Subject, + CertificateAuthorizationDefaults.ClientCertificateHeader); + string b64 = Convert.ToBase64String(certificate.Export(X509ContentType.Cert)); client.DefaultRequestHeaders.Add(CertificateAuthorizationDefaults.ClientCertificateHeader, b64); } diff --git a/src/Security/src/Authorization.Certificate/GlobalUsings.cs b/src/Security/src/Authorization.Certificate/GlobalUsings.cs index 2b0224ece7..e76bd93454 100644 --- a/src/Security/src/Authorization.Certificate/GlobalUsings.cs +++ b/src/Security/src/Authorization.Certificate/GlobalUsings.cs @@ -5,3 +5,5 @@ global using System.Security.Cryptography.X509Certificates; global using Microsoft.AspNetCore.Authorization; global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; +global using Steeltoe.Common.Options; diff --git a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs index 805dad3a47..ff7194022c 100644 --- a/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs +++ b/src/Security/src/Authorization.Certificate/PostConfigureCertificateAuthenticationOptions.cs @@ -2,25 +2,23 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System.Runtime.ConstrainedExecution; using System.Security.Claims; using Microsoft.AspNetCore.Authentication.Certificate; -using Microsoft.Extensions.Options; using Steeltoe.Common; -using Steeltoe.Common.Options; namespace Steeltoe.Security.Authorization.Certificate; -public sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions +public sealed class PostConfigureCertificateAuthenticationOptions : IPostConfigureOptions { - private readonly IOptionsMonitor _certificateOptions; + private readonly IOptionsMonitor _certificateOptionsMonitor; private readonly ILogger _logger; - public PostConfigureCertificateAuthenticationOptions(IOptionsMonitor certificateOptions, ILogger logger) + public PostConfigureCertificateAuthenticationOptions(IOptionsMonitor certificateOptionsMonitor, + ILogger logger) { - ArgumentGuard.NotNull(certificateOptions); + ArgumentGuard.NotNull(certificateOptionsMonitor); - _certificateOptions = certificateOptions; + _certificateOptionsMonitor = certificateOptionsMonitor; _logger = logger; } @@ -28,21 +26,24 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options { ArgumentGuard.NotNull(options); - CertificateOptions named = _certificateOptions.Get("ContainerIdentity"); + CertificateOptions containerIdentityOptions = _certificateOptionsMonitor.Get("ContainerIdentity"); options.ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust; - options.ClaimsIssuer = named.Certificate?.Issuer; + options.ClaimsIssuer = containerIdentityOptions.Certificate?.Issuer; options.RevocationMode = X509RevocationMode.NoCheck; string? systemCertPath = Environment.GetEnvironmentVariable("CF_SYSTEM_CERT_PATH"); + if (!string.IsNullOrEmpty(systemCertPath)) { - IEnumerable systemCertificates = Directory.GetFiles(systemCertPath).Select(certificateFilename => new X509Certificate2(certificateFilename)); + IEnumerable systemCertificates = + Directory.GetFiles(systemCertPath).Select(certificateFilename => new X509Certificate2(certificateFilename)); + options.CustomTrustStore.AddRange(systemCertificates.ToArray()); } - if (named.IssuerChain.Any()) + if (containerIdentityOptions.IssuerChain.Any()) { - options.AdditionalChainCertificates.AddRange(named.IssuerChain.ToArray()); + options.AdditionalChainCertificates.AddRange(containerIdentityOptions.IssuerChain.ToArray()); } options.Events = new CertificateAuthenticationEvents @@ -58,10 +59,16 @@ public void PostConfigure(string? name, CertificateAuthenticationOptions options if (ApplicationInstanceCertificate.TryParse(context.ClientCertificate, out ApplicationInstanceCertificate? clientCertificate, _logger)) { - claims.Add(new Claim(ApplicationClaimTypes.OrganizationId, clientCertificate.OrganizationId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + claims.Add(new Claim(ApplicationClaimTypes.OrganizationId, clientCertificate.OrganizationId, ClaimValueTypes.String, + context.Options.ClaimsIssuer)); + claims.Add(new Claim(ApplicationClaimTypes.SpaceId, clientCertificate.SpaceId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); - claims.Add(new Claim(ApplicationClaimTypes.ApplicationId, clientCertificate.ApplicationId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); - claims.Add(new Claim(ApplicationClaimTypes.ApplicationInstanceId, clientCertificate.InstanceId, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + + claims.Add(new Claim(ApplicationClaimTypes.ApplicationId, clientCertificate.ApplicationId, ClaimValueTypes.String, + context.Options.ClaimsIssuer)); + + claims.Add(new Claim(ApplicationClaimTypes.ApplicationInstanceId, clientCertificate.InstanceId, ClaimValueTypes.String, + context.Options.ClaimsIssuer)); } var identity = new ClaimsIdentity(claims, CertificateAuthenticationDefaults.AuthenticationScheme); diff --git a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt index 830ba6dec5..0fe6789f0a 100644 --- a/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt +++ b/src/Security/src/Authorization.Certificate/PublicAPI.Unshipped.txt @@ -18,7 +18,7 @@ Steeltoe.Security.Authorization.Certificate.CertificateAuthorizationPolicyBuilde Steeltoe.Security.Authorization.Certificate.CertificateServiceCollectionExtensions Steeltoe.Security.Authorization.Certificate.PostConfigureCertificateAuthenticationOptions Steeltoe.Security.Authorization.Certificate.PostConfigureCertificateAuthenticationOptions.PostConfigure(string? name, Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationOptions! options) -> void -Steeltoe.Security.Authorization.Certificate.PostConfigureCertificateAuthenticationOptions.PostConfigureCertificateAuthenticationOptions(Microsoft.Extensions.Options.IOptionsMonitor! certificateOptions, Microsoft.Extensions.Logging.ILogger! logger) -> void +Steeltoe.Security.Authorization.Certificate.PostConfigureCertificateAuthenticationOptions.PostConfigureCertificateAuthenticationOptions(Microsoft.Extensions.Options.IOptionsMonitor! certificateOptionsMonitor, Microsoft.Extensions.Logging.ILogger! logger) -> void Steeltoe.Security.Authorization.Certificate.SameOrgRequirement Steeltoe.Security.Authorization.Certificate.SameOrgRequirement.SameOrgRequirement() -> void Steeltoe.Security.Authorization.Certificate.SameSpaceRequirement diff --git a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs index c0b418bb51..69d399c858 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/JwtBearerServiceCollectionExtensionsTest.cs @@ -4,6 +4,7 @@ using FluentAssertions; using Microsoft.Extensions.DependencyInjection; +using Steeltoe.Security.Authentication.Shared; namespace Steeltoe.Security.Authentication.JwtBearer.Test; @@ -25,8 +26,8 @@ public void ConfigureJwtBearerForCloudFoundry_ConfiguresHttpClient() { var serviceCollection = new ServiceCollection(); - var serviceProvider = serviceCollection.ConfigureJwtBearerForCloudFoundry().BuildServiceProvider(); - var httpClient = serviceProvider.GetRequiredService().CreateClient("SteeltoeSecurity"); + ServiceProvider serviceProvider = serviceCollection.ConfigureJwtBearerForCloudFoundry().BuildServiceProvider(); + HttpClient httpClient = serviceProvider.GetRequiredService().CreateClient(SteeltoeSecurityDefaults.HttpClientName); httpClient.Timeout.Should().Be(new TimeSpan(0, 0, 0, 60)); } diff --git a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs index 961fde9f7c..1a0f9c51c0 100644 --- a/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs +++ b/src/Security/test/Authentication.JwtBearer.Test/PostConfigureJwtBearerOptionsTest.cs @@ -22,11 +22,13 @@ public void PostConfigure_AddsClientIdToValidAudiences() { { "Authentication:Schemes:Bearer:ClientId", "testClient" } }; + var jwtBearerOptions = new JwtBearerOptions { Backchannel = new HttpClient() }; - var configuration = new ConfigurationBuilder().AddInMemoryCollection(appSettings).Build(); + + IConfigurationRoot configuration = new ConfigurationBuilder().AddInMemoryCollection(appSettings).Build(); var postConfigurer = new PostConfigureJwtBearerOptions(configuration, null!); postConfigurer.PostConfigure(null, jwtBearerOptions); @@ -37,12 +39,14 @@ public void PostConfigure_AddsClientIdToValidAudiences() [Fact] public void PostConfigure_ConfiguresForLocalUAA() { - var configuration = new ConfigurationBuilder().Build(); + IConfigurationRoot configuration = new ConfigurationBuilder().Build(); + var jwtBearerOptions = new JwtBearerOptions { Authority = SteeltoeSecurityDefaults.LocalUAAPath, Backchannel = new HttpClient() }; + jwtBearerOptions.RequireHttpsMetadata.Should().BeTrue(); jwtBearerOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().BeNull(); @@ -82,15 +86,15 @@ public void PostConfigure_ConfiguresForCloudFoundry() }] } """; + using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", vcapServices); - var configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); + IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(configuration); serviceCollection.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(); serviceCollection.ConfigureJwtBearerForCloudFoundry(); - var jwtBearerOptions = serviceCollection - .BuildServiceProvider().GetRequiredService>() + JwtBearerOptions jwtBearerOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() .Get(JwtBearerDefaults.AuthenticationScheme); jwtBearerOptions.Authority.Should().Be("https://steeltoe.login.sys.cf-app.com"); diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs index d04a3e1a3c..8b3c93cd45 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/OpenIdConnectServiceCollectionExtensionsTest.cs @@ -17,6 +17,7 @@ public void ConfigureOpenIdConnectForCloudFoundry_AddsExpectedRegistrations() serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); + serviceCollection.Should().Contain(service => service.ServiceType == typeof(IHttpClientFactory)); serviceCollection.Should().Contain(service => service.ImplementationType == typeof(PostConfigureOpenIdConnectOptions)); } @@ -25,8 +26,8 @@ public void ConfigureOpenIdConnectForCloudFoundry_ConfiguresHttpClient() { var serviceCollection = new ServiceCollection(); - var serviceProvider = serviceCollection.ConfigureOpenIdConnectForCloudFoundry().BuildServiceProvider(); - var httpClient = serviceProvider.GetRequiredService().CreateClient(SteeltoeSecurityDefaults.HttpClientName); + ServiceProvider serviceProvider = serviceCollection.ConfigureOpenIdConnectForCloudFoundry().BuildServiceProvider(); + HttpClient httpClient = serviceProvider.GetRequiredService().CreateClient(SteeltoeSecurityDefaults.HttpClientName); httpClient.Timeout.Should().Be(new TimeSpan(0, 0, 0, 60)); } diff --git a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs index 623b03f699..686caf9cbf 100644 --- a/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs +++ b/src/Security/test/Authentication.OpenIdConnect.Test/PostConfigureOpenIdConnectOptionsTest.cs @@ -2,13 +2,11 @@ // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System.Security.Claims; using FluentAssertions; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Moq; using Steeltoe.Common.TestResources; using Steeltoe.Configuration.CloudFoundry.ServiceBinding; using Steeltoe.Security.Authentication.Shared; @@ -23,14 +21,15 @@ public void PostConfigure_AddsClientIdToValidAudiences() var appSettings = new Dictionary { { "Authentication:Schemes:OpenIdConnect:Authority", "https://authority.com" }, - { "Authentication:Schemes:OpenIdConnect:ClientId", "testClient" }, + { "Authentication:Schemes:OpenIdConnect:ClientId", "testClient" } }; + IConfigurationRoot configuration = new ConfigurationBuilder().AddInMemoryCollection(appSettings).Build(); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(configuration); serviceCollection.AddAuthentication().AddOpenIdConnect(); - OpenIdConnectOptions openIdConnectOptions = serviceCollection - .BuildServiceProvider().GetRequiredService>() + + OpenIdConnectOptions openIdConnectOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() .Get(OpenIdConnectDefaults.AuthenticationScheme); var postConfigurer = new PostConfigureOpenIdConnectOptions(null!); @@ -48,6 +47,7 @@ public void PostConfigure_ConfiguresForLocalUAA() Authority = SteeltoeSecurityDefaults.LocalUAAPath, Backchannel = new HttpClient() }; + openIdConnectOptions.RequireHttpsMetadata.Should().BeTrue(); openIdConnectOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().BeNull(); @@ -87,6 +87,7 @@ public void PostConfigure_ConfiguresForCloudFoundry() }] } """; + using var servicesScope = new EnvironmentVariableScope("VCAP_SERVICES", vcapServices); IConfigurationRoot configuration = new ConfigurationBuilder().AddCloudFoundryServiceBindings().Build(); var serviceCollection = new ServiceCollection(); @@ -95,8 +96,7 @@ public void PostConfigure_ConfiguresForCloudFoundry() serviceCollection.AddAuthentication().AddOpenIdConnect(); serviceCollection.ConfigureOpenIdConnectForCloudFoundry(); - OpenIdConnectOptions openIdConnectOptions = serviceCollection - .BuildServiceProvider().GetRequiredService>() + OpenIdConnectOptions openIdConnectOptions = serviceCollection.BuildServiceProvider().GetRequiredService>() .Get(OpenIdConnectDefaults.AuthenticationScheme); openIdConnectOptions.Authority.Should().Be("https://steeltoe.login.sys.cf-app.com"); @@ -106,13 +106,4 @@ public void PostConfigure_ConfiguresForCloudFoundry() openIdConnectOptions.TokenValidationParameters.IssuerSigningKeyResolver.Should().NotBeNull(); openIdConnectOptions.TokenValidationParameters.ValidAudience.Should().Be("4e6f8e34-f42b-440e-a042-f2b13c1d5bed"); } - - ////[Fact] - ////public void MapScopesToClaims_MapsScopesToClaims() - ////{ - //// var claimsIdentity = new Mock(); - //// claimsIdentity. - //// var context = new Mock(); - //// context.Setup(ctx => ctx.Principal!.AddIdentity(claimsIdentity.Object)); - ////} } diff --git a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs index 2ebbb2650d..400da2189b 100644 --- a/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs +++ b/src/Security/test/Authorization.Certificate.Test/CertificateAuthorizationTest.cs @@ -31,7 +31,7 @@ public async Task CertificateAuth_AcceptsSameOrg() using IHost host = await GetHostBuilder().StartAsync(); var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); - var httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch); + HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.OrgAndSpaceMatch); HttpResponseMessage response = await httpClient.GetAsync(requestUri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -96,19 +96,15 @@ public async Task CertificateAuth_AcceptsSameOrg_DiegoCert() using IHost host = await GetHostBuilder().StartAsync(); var requestUri = new Uri($"https://localhost/{CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy}"); - var httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego); + HttpClient httpClient = ClientWithCertificate(host.GetTestClient(), Certificates.FromDiego); HttpResponseMessage response = await httpClient.GetAsync(requestUri); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } - // MIIECDCCAvCgAwIBAgIRAKiNf1klu0/IdgHxirEAxJkwDQYJKoZIhvcNAQELBQAwMjEwMC4GA1UEAxMnRGllZ28gSW5zdGFuY2UgSWRlbnRpdHkgSW50ZXJtZWRpYXRlIENBMB4XDTI0MDUyODIwNTkyNloXDTI0MDUyOTIwNTkyNlowgcgxgZ4wLwYDVQQLEyhhcHA6ZmU5MjUzYzYtNTQ5Yi00Mzg1LWI2OWQtMmVjMDMyOGViMjk2MDEGA1UECxMqc3BhY2U6YWI2MGFhYzItZmI2NC00M2FiLWJhMjQtYzU3YTE1YTdlMTE0MDgGA1UECxMxb3JnYW5pemF0aW9uOjdmZTRkMDI3LTIwNTgtNDUzOS1hNDBjLTcwMmFjMTM3MzkwNTElMCMGA1UEAxMcOGE3YzE4ZmUtYWI1NC00MDlmLTU5YTctNzhmYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMj1sd7l3cRgxvxwlH/N3wW9SiRoT9fv0DZ0D1x5SM7bO4kHryOM4DyrF5YmJVICfgGJ5qI8hx8ohJD+MSam68gt4v1Isy5mcL7j4yMfRLN63TVM1w1vikTtxRa/cCWPl5ex+whKiGmu7j5fksntIzaW60wgx+TKOYV5Ukr40nKkmqytpqUIiG1QlvdptZjRP8WblaQ5zRyskIm33qTqjeaz/HgKmcsCLfN3+EgvIG0Oq8kqXW65BAzZJwXsy1cbyrG6tqKKL9foXKfkecnD9oIL46hZZk8lOdQO9UI0c8PA+FpMzBgftaT3MtxlTuxH8zRAAX0rRmSyxILiI0NWxBcCAwEAAaOBgTB/MA4GA1UdDwEB/wQEAwIDqDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUSr6rDD3+zF8WowftQLoFo+ldyhswLQYDVR0RBCYwJIIcOGE3YzE4ZmUtYWI1NC00MDlmLTU5YTctNzhmYYcECv+J+DANBgkqhkiG9w0BAQsFAAOCAQEAFA1Px9XzccEWv1tvtFdJ8G6KHXOeOO5009RqdrUepHQLXIxUqs1VzYvmXv9FiG/Ahf4UyS/KrhX7gNgPN3562tgmiYRzUKSPY5qR6dFXydo+9GoDsx2r64JmX2OTdkHTFk2g0dnUWkMKmhlSHnYI+X9lvvAkKbQDwo+1qVCvjdX55POmHVWQxwDZnnO381F0OAg4Wq89hIfXodPCK9cmBUd0fjpJikAq4064wxVpt/IQrs9RLtN3nhLhHvzK5UkTuI22gyrZHF+4mnB0NfpxQRxnDApHGKDOWrN5g1FltXLiS8rX1znYFIZ0XbRXqC9jS0SJHVZt9QIdVxwTugNLHQ== - - private IHostBuilder GetHostBuilder() { - return new HostBuilder() - .ConfigureAppConfiguration(builder => builder.AddContainerIdentityCertificate(fixture.ServerOrgId, fixture.ServerSpaceId)) + return new HostBuilder().ConfigureAppConfiguration(builder => builder.AddContainerIdentityCertificate(fixture.ServerOrgId, fixture.ServerSpaceId)) .ConfigureWebHostDefaults(webHost => webHost.UseStartup()).ConfigureWebHost(webBuilder => webBuilder.UseTestServer()); } @@ -122,13 +118,16 @@ private static HttpClient ClientWithCertificate(HttpClient httpClient, X509Certi private static class Certificates { - private static readonly Func GetFilePath = fileName => Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", fileName); + private static readonly Func GetFilePath = fileName => + Path.Combine(LocalCertificateWriter.ApplicationBasePath, "GeneratedCertificates", fileName); - public static X509Certificate2 OrgAndSpaceMatch { get; } = X509Certificate2.CreateFromPemFile(GetFilePath("OrgAndSpaceMatchCert.pem"), GetFilePath("OrgAndSpaceMatchKey.pem")); + public static X509Certificate2 OrgAndSpaceMatch { get; } = + X509Certificate2.CreateFromPemFile(GetFilePath("OrgAndSpaceMatchCert.pem"), GetFilePath("OrgAndSpaceMatchKey.pem")); public static X509Certificate2 OrgMatch { get; } = X509Certificate2.CreateFromPemFile(GetFilePath("OrgMatchCert.pem"), GetFilePath("OrgMatchKey.pem")); - public static X509Certificate2 SpaceMatch { get; } = X509Certificate2.CreateFromPemFile(GetFilePath("SpaceMatchCert.pem"), GetFilePath("SpaceMatchKey.pem")); + public static X509Certificate2 SpaceMatch { get; } = + X509Certificate2.CreateFromPemFile(GetFilePath("SpaceMatchCert.pem"), GetFilePath("SpaceMatchKey.pem")); public static X509Certificate2 FromDiego { get; } = X509Certificate2.CreateFromPemFile("instance.crt", "instance.key"); } diff --git a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs index e2ea1d2825..2c8319f185 100644 --- a/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs +++ b/src/Security/test/Authorization.Certificate.Test/TestServerCertificateStartup.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; @@ -17,19 +18,12 @@ public sealed class TestServerCertificateStartup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddCertificateAuthorizationServer(Configuration); - services - .AddAuthentication() - .AddCertificate(options => - { - options.ValidateValidityPeriod = false; - options.Events = new() - { - OnCertificateValidated = context => - { - return Task.CompletedTask; - } - }; - }); + + services.AddAuthentication().AddCertificate(options => + { + options.ValidateValidityPeriod = false; + }); + services.AddAuthorization(options => { options.AddPolicy(CertificateAuthorizationDefaults.SameOrganizationAuthorizationPolicy, authorizationPolicyBuilder => diff --git a/src/Steeltoe.Connectors.slnf b/src/Steeltoe.Connectors.slnf index f0bb12eafe..6eb874b865 100644 --- a/src/Steeltoe.Connectors.slnf +++ b/src/Steeltoe.Connectors.slnf @@ -12,10 +12,8 @@ "Configuration\\src\\CloudFoundry\\Steeltoe.Configuration.CloudFoundry.csproj", "Configuration\\src\\Kubernetes.ServiceBinding\\Steeltoe.Configuration.Kubernetes.ServiceBinding.csproj", "Connectors\\src\\Abstractions\\Steeltoe.Connectors.Abstractions.csproj", - "Connectors\\src\\CloudFoundry\\Steeltoe.Connectors.CloudFoundry.csproj", "Connectors\\src\\Connectors\\Steeltoe.Connectors.csproj", "Connectors\\src\\EntityFrameworkCore\\Steeltoe.Connectors.EntityFrameworkCore.csproj", - "Connectors\\test\\CloudFoundry.Test\\Steeltoe.Connectors.CloudFoundry.Test.csproj", "Connectors\\test\\Connectors.Test\\Steeltoe.Connectors.Test.csproj", "Connectors\\test\\EntityFrameworkCore.Test\\Steeltoe.Connectors.EntityFrameworkCore.Test.csproj" ] diff --git a/src/Steeltoe.Security.slnf b/src/Steeltoe.Security.slnf index 0fcdd2e030..7e4c153b98 100644 --- a/src/Steeltoe.Security.slnf +++ b/src/Steeltoe.Security.slnf @@ -13,13 +13,16 @@ "Configuration\\src\\CloudFoundry\\Steeltoe.Configuration.CloudFoundry.csproj", "Configuration\\src\\Kubernetes.ServiceBinding\\Steeltoe.Configuration.Kubernetes.ServiceBinding.csproj", "Connectors\\src\\Abstractions\\Steeltoe.Connectors.Abstractions.csproj", - "Connectors\\src\\CloudFoundry\\Steeltoe.Connectors.CloudFoundry.csproj", "Connectors\\src\\Connectors\\Steeltoe.Connectors.csproj", - "Security\\src\\Authentication.CloudFoundry\\Steeltoe.Security.Authentication.CloudFoundry.csproj", - "Security\\src\\Authentication.Mtls\\Steeltoe.Security.Authentication.Mtls.csproj", + "Security\\src\\Authentication.Jwt\\Steeltoe.Security.Authentication.JwtBearer.csproj", + "Security\\src\\Authentication.OpenIdConnect\\Steeltoe.Security.Authentication.OpenIdConnect.csproj", + "Security\\src\\Authentication.Shared\\Steeltoe.Security.Authentication.Shared.csproj", + "Security\\src\\Authorization.Certificate\\Steeltoe.Security.Authorization.Certificate.csproj", "Security\\src\\DataProtection.Redis\\Steeltoe.Security.DataProtection.Redis.csproj", - "Security\\test\\Authentication.CloudFoundry.Test\\Steeltoe.Security.Authentication.CloudFoundry.Test.csproj", - "Security\\test\\Authentication.Mtls.Test\\Steeltoe.Security.Authentication.Mtls.Test.csproj", + "Security\\test\\Authentication.JwtBearer.Test\\Steeltoe.Security.Authentication.JwtBearer.Test.csproj", + "Security\\test\\Authentication.OpenIdConnect.Test\\Steeltoe.Security.Authentication.OpenIdConnect.Test.csproj", + "Security\\test\\Authentication.Shared.Test\\Steeltoe.Security.Authentication.Shared.Test.csproj", + "Security\\test\\Authorization.Certificate.Test\\Steeltoe.Security.Authorization.Certificate.Test.csproj", "Security\\test\\DataProtection.Redis.Test\\Steeltoe.Security.DataProtection.Redis.Test.csproj" ] }