Skip to content

Commit

Permalink
Merge branch 'main' into Fix-microsoft#34-issue-with-Get-SPIDMetadata…
Browse files Browse the repository at this point in the history
…s-PS-script
  • Loading branch information
fume committed Sep 16, 2022
2 parents 03125c9 + 5ec7cc6 commit 38024c2
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class ProxyController : Controller
private readonly ILogAccessService _logAccessService;
private readonly IFederatorResponseService _federatorResponseService;
private readonly IFederatorRequestService _federatorRequestService;
private readonly ISAMLService _samlService;
private readonly FederatorOptions _federatorOptions;
private readonly TechnicalChecksOptions _technicalChecksOptions;
private readonly LoggingOptions _loggingOptions;
Expand All @@ -23,6 +24,7 @@ public class ProxyController : Controller
ILogAccessService logAccessService,
IFederatorResponseService federatorResponseService,
IFederatorRequestService federatorRequestService,
ISAMLService samlService,
IOptions<FederatorOptions> federatorOptions,
IOptions<TechnicalChecksOptions> technicalChecksOptions,
IOptions<LoggingOptions> loggingOptions)
Expand All @@ -31,6 +33,7 @@ public class ProxyController : Controller
_logAccessService = logAccessService;
_federatorResponseService = federatorResponseService;
_federatorRequestService = federatorRequestService;
_samlService = samlService;
_federatorOptions = federatorOptions.Value;
_technicalChecksOptions = technicalChecksOptions.Value;
_loggingOptions = loggingOptions.Value;
Expand Down Expand Up @@ -170,7 +173,7 @@ public async Task<ActionResult> AssertionConsumer(string SAMLResponse, string Re
_logger.LogDebug("Removing NameQualifier from Issuer");
_logger.LogDebug("Setting new SubjectConfirmationData.Recipient = {newRecipient}", _federatorOptions.FederatorAttributeConsumerServiceUrl);
responseXml.AlterAudience(_federatorOptions.EntityId)
.AlterDestination(_federatorOptions.FederatorAttributeConsumerServiceUrl, HttpContext.Request.Host.ToString(), _technicalChecksOptions.SkipTechnicalChecks)
.AlterDestination(_federatorOptions.FederatorAttributeConsumerServiceUrl, _samlService.GetAttributeConsumerService(), _technicalChecksOptions.SkipTechnicalChecks)
.AlterSubjectConfirmation(_federatorOptions.FederatorAttributeConsumerServiceUrl)
.RemoveNameQualifierIfFormatEntity();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
<PackageReference Include="Azure.Identity" Version="1.6.0" />
<PackageReference Include="bootstrap" Version="5.1.2" />
<PackageReference Include="jQuery" Version="3.6.0" />
<PackageReference Include="jQuery.Validation" Version="1.19.3" />
Expand All @@ -40,4 +42,7 @@
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
<PackageReference Include="System.Security.Cryptography.Csp" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="SigningCert\" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ public static XmlDocument RemoveUncompliantAuthnContextClassrefs(this XmlDocumen
Purpose.InnerText = purposeValue;

Extensions.AppendChild(Purpose);
rootEl.AppendChild(Extensions);
var issuer = samlRequest.GetElementsByTagName("Issuer", "*")[0];

rootEl.InsertAfter(Extensions,issuer);
}

return samlRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,18 @@ public static XmlDocument RemoveSignatures(this XmlDocument samlResponse)
}

public static XmlDocument AlterDestination(this XmlDocument samlResponse,
string attributeConsumerServiceUrl,
string baseHost,
string federatorAttributeConsumerServiceUrl,
string spidProxyAttributeConsumerEndpoint,
bool skipTechnicalChecks)
{
var root = samlResponse.DocumentElement;
if (!root.HasAttribute("Destination") || string.IsNullOrWhiteSpace(root.GetAttribute("Destination")))
throw new SPIDValidationException("missing Destination");
var destValue = root.GetAttribute("Destination");
if (destValue != $"https://{baseHost}/proxy/assertionconsumer" && !skipTechnicalChecks)
if (destValue != spidProxyAttributeConsumerEndpoint && !skipTechnicalChecks)
throw new SPIDValidationException("different Destination");

var newDestination = attributeConsumerServiceUrl;
var newDestination = federatorAttributeConsumerServiceUrl;
root.SetAttribute("Destination", newDestination);

return samlResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Azure.Identity;

namespace Microsoft.SPID.Proxy.Models.Extensions
{
public static class WebApplicationBuilderExtesions
{
public static WebApplicationBuilder AddKeyVaultConfigurationProvider(this WebApplicationBuilder builder)
{
if (!string.IsNullOrEmpty(builder.Configuration["KeyVaultName"]))
{
builder.Configuration.AddAzureKeyVault(
new Uri($"https://{builder.Configuration["KeyVaultName"]}.vault.azure.net/"),
new DefaultAzureCredential());
}

return builder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,18 @@ namespace Microsoft.SPID.Proxy.Models.Options;

public class CertificateOptions
{
public CertificateLocation CertLocation { get; set; }
/// <summary>
/// If the CertLocation is ServerRelativeStorage, define the filename in the server relative file folder SigningCert
/// if the CertLocation is KeyVault, define the certificate name loaded from the KeyVault
/// </summary>
public string CertName { get; set; }
public string CertPassword { get; set; }
public int CacheAbsoluteExpirationMinutes { get; set; } = 360;
}

public enum CertificateLocation
{
ServerRelativeStorage = 0,
KeyVault = 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.AspNetCore.HttpOverrides;
using System.Net;

namespace Microsoft.SPID.Proxy.Models.Options
{
public class ConfigureForwardedHeadersOptions : IConfigureOptions<ForwardedHeadersOptions>
{
private readonly IConfiguration _configuration;
public ConfigureForwardedHeadersOptions(IConfiguration config)
{
_configuration = config;
}

public void Configure(ForwardedHeadersOptions options)
{
var section = _configuration.GetSection("ForwardedHeaders");
section.Bind(options);

var knownProxies = section.GetValue<string>("KnownProxies")?.Split(',');
var knownNetworks = section.GetValue<string>("KnownNetworks")?.Split(',');
var allowedHosts = section.GetValue<string>("AllowedHosts")?.Split(',');

if (knownProxies?.Length > 0)
{
options.KnownProxies.Clear();
foreach (var ip in knownProxies)
{
options.KnownProxies.Add(IPAddress.Parse(ip));
}
}

if (knownNetworks?.Length > 0)
{
options.KnownNetworks.Clear();
foreach (var network in knownNetworks)
{
var networkSplit = network.Split('/');
var prefix = networkSplit[0];
var prefixLength = int.Parse(networkSplit[1]);

options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse(prefix), prefixLength));
}
}

if(allowedHosts?.Length > 0)
{
options.AllowedHosts.Clear();
options.AllowedHosts = allowedHosts;
}
}
}
}
12 changes: 10 additions & 2 deletions WebApps/Proxy/Microsoft.SPID.Proxy/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Logging.EventLog;
using System.Net;

var builder = WebApplication.CreateBuilder(args);
builder.AddKeyVaultConfigurationProvider();

if (string.Equals(builder.Configuration["ASPNETCORE_FORWARDEDHEADERS_ENABLED"], "true", StringComparison.OrdinalIgnoreCase))
{
builder.Services.AddTransient<IConfigureOptions<ForwardedHeadersOptions>, ConfigureForwardedHeadersOptions>();
}

// Add services to the container.
builder.Services.AddRazorPages();
Expand Down Expand Up @@ -53,8 +61,8 @@

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,72 +11,95 @@ namespace Microsoft.SPID.Proxy.Services.Implementations;

public class CertificateService : ICertificateService
{
private X509Certificate2 _certificate;
private readonly IHostEnvironment _hostEnvironment;
private readonly IDistributedCache _cache;
private readonly ILogger _logger;
private readonly FederatorOptions _federatorOptions;
private readonly CertificateOptions _certificateOptions;
private X509Certificate2 _certificate;
private readonly IHostEnvironment _hostEnvironment;
private readonly IDistributedCache _cache;
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
private readonly CertificateOptions _certificateOptions;

public CertificateService(IHostEnvironment hostEnvironment,
IDistributedCache cache,
ILogger<CertificateService> logger,
IOptions<FederatorOptions> federatorOptions,
IOptions<CertificateOptions> certificateOptions)
{
_hostEnvironment = hostEnvironment;
_cache = cache;
_logger = logger;
_federatorOptions = federatorOptions.Value;
_certificateOptions = certificateOptions.Value;
}
public CertificateService(IHostEnvironment hostEnvironment,
IDistributedCache cache,
ILogger<CertificateService> logger,
IConfiguration configuration,
IOptions<CertificateOptions> certificateOptions)
{
_hostEnvironment = hostEnvironment;
_cache = cache;
_logger = logger;
_configuration = configuration;
_certificateOptions = certificateOptions.Value;
}

public async Task<X509Certificate2> GetProxySignCertificate()
{
string certName = _certificateOptions.CertName;
public async Task<X509Certificate2> GetProxySignCertificate()
{
string certName = _certificateOptions.CertName;

_logger.LogDebug("Getting {certName} certificate", certName);
_logger.LogDebug("Getting {certName} certificate", certName);

if (_certificate != null)
{
_logger.LogDebug("Returning pre-loaded certificate.");
return _certificate;
}
if (_certificate != null)
{
_logger.LogDebug("Returning pre-loaded certificate.");
return _certificate;
}

if (_cache != null)
{
var certBytes = await _cache.GetAsync($"SigningCert_{certName}");
if (certBytes != null)
{
_logger.LogDebug("Certificate retrieved from cache");
_certificate = new X509Certificate2(certBytes);
return _certificate;
}
}
if (_cache != null)
{
var certBytes = await _cache.GetAsync($"SigningCert_{certName}");
if (certBytes != null)
{
_logger.LogDebug("Certificate retrieved from cache");
_certificate = new X509Certificate2(certBytes);
return _certificate;
}
}

var certpath = Path.Combine(_hostEnvironment.ContentRootPath, "SigningCert", certName);
_certificate = LoadCertificate();

_logger.LogDebug("Loading certificate from filesystem: {certPath}", certpath);
if (_cache != null)
{
_logger.LogDebug("Storing certificate in cache");
var certBytes = _certificate.Export(X509ContentType.Pfx);
var options = new DistributedCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(_certificateOptions.CacheAbsoluteExpirationMinutes),
SlidingExpiration = null
};
await _cache.SetAsync($"SigningCert_{certName}", certBytes, options);
}

_certificate =
new X509Certificate2(certpath,
_certificateOptions.CertPassword,
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable);
return _certificate;
}

if (_cache != null)
{
_logger.LogDebug("Storing certificate in cache");
var certBytes = _certificate.Export(X509ContentType.Pfx);
var options = new DistributedCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(_certificateOptions.CacheAbsoluteExpirationMinutes),
SlidingExpiration = null
};
await _cache.SetAsync($"SigningCert_{certName}", certBytes, options);
}

return _certificate;
}
private X509Certificate2 LoadCertificate()
{
X509Certificate2 cert = null;
switch (_certificateOptions.CertLocation)
{
case CertificateLocation.ServerRelativeStorage:
var certpath = Path.Combine(_hostEnvironment.ContentRootPath, "SigningCert", _certificateOptions.CertName);
_logger.LogDebug("Loading certificate from filesystem: {certPath}", certpath);
cert =
new X509Certificate2(certpath,
_certificateOptions.CertPassword,
X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable);
break;
case CertificateLocation.KeyVault:
var keyVaultName = _configuration.GetValue<string>("KeyVaultName");
if (!string.IsNullOrEmpty(keyVaultName))
{
_logger.LogDebug($"Loading certificate from KeyVault: {keyVaultName}");
var certBase64 = _configuration.GetValue<string>(_certificateOptions.CertName);
cert = new X509Certificate2(Convert.FromBase64String(certBase64), (string)null, X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
}
else
{
throw new InvalidOperationException($"The CertificateLocation is KeyVault, but KeyVaultName is null");
}
break;
}
return cert;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class SAMLService : ISAMLService

public string GetAttributeConsumerService()
{
return $"https://{_httpContextAccessor.HttpContext.Request.Host}/proxy/assertionconsumer";
return $"{_httpContextAccessor.HttpContext.Request.Scheme}://{_httpContextAccessor.HttpContext.Request.Host}/proxy/assertionconsumer";

}

Expand Down

0 comments on commit 38024c2

Please sign in to comment.