Created
April 17, 2020 06:33
-
-
Save MatthewDavidCampbell/b64fbc10becd87bb35c1dc81c2c0c96b to your computer and use it in GitHub Desktop.
Kestrel 3.1 Service Fabric Https certificate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<ApplicationManifest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ApplicationTypeName="Biometria.Produktion.PublicApi.ApplicationType" ApplicationTypeVersion="1.0.0" xmlns="http://schemas.microsoft.com/2011/01/fabric"> | |
<Parameters> | |
<!-- Commented out parameters but MyCert would slide in here --> | |
</Parameters> | |
<!-- Import the ServiceManifest from the ServicePackage. The ServiceManifestName and ServiceManifestVersion | |
should match the Name and Version attributes of the ServiceManifest element defined in the | |
ServiceManifest.xml file. --> | |
<ServiceManifestImport> | |
<ServiceManifestRef ServiceManifestName="Gary.ServicePkg" ServiceManifestVersion="1.0.0" /> | |
<ConfigOverrides> | |
<ConfigOverride Name="Config"> | |
<Settings> | |
<!-- Commented out sections --> | |
</Settings> | |
</ConfigOverride> | |
</ConfigOverrides> | |
<EnvironmentOverrides CodePackageRef="Code"> | |
<!-- Commented out environment variables --> | |
</EnvironmentOverrides> | |
<Policies> | |
<EndpointBindingPolicy EndpointRef="KestrelEndpointHttps" CertificateRef="MyCert" /> | |
</Policies> | |
</ServiceManifestImport> | |
<DefaultServices> | |
<!-- The section below creates instances of service types, when an instance of this | |
application type is created. You can also create one or more instances of service type using the | |
ServiceFabric PowerShell module. | |
The attribute ServiceTypeName below must match the name defined in the imported ServiceManifest.xml file. --> | |
<Service Name="Gary.Service" ServicePackageActivationMode="ExclusiveProcess"> | |
<StatelessService ServiceTypeName="Gary.ServiceType" InstanceCount="1"> | |
<SingletonPartition /> | |
</StatelessService> | |
</Service> | |
</DefaultServices> | |
<Certificates> | |
<EndpointCertificate X509FindValue="[MyCert]" Name="MyCert" /> | |
</Certificates> | |
</ApplicationManifest> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Fabric; | |
using System.Fabric.Description; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.Extensions.Hosting; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.ServiceFabric.Services.Communication.Runtime; | |
using Microsoft.AspNetCore.Server.Kestrel.Core; | |
using System.Net; | |
using System.Security.Cryptography.X509Certificates; | |
using Microsoft.Extensions.Logging; | |
namespace Gary.Service | |
{ | |
public class LocalKestrelCommunicationListener : ICommunicationListener | |
{ | |
public LocalKestrelCommunicationListener( | |
StatelessServiceContext context, | |
Action<IWebHostBuilder> configureKestrel | |
) | |
{ | |
(Context, ConfigureKestrel) = (context, configureKestrel); | |
} | |
public LocalKestrelCommunicationListener( | |
StatelessServiceContext context, | |
Action<IWebHostBuilder> configureKestrel, | |
string endpointName | |
) | |
{ | |
(Context, ConfigureKestrel, EndpointName) = (context, configureKestrel, endpointName); | |
} | |
/// <summary> | |
/// Force stateless services | |
/// </summary> | |
protected StatelessServiceContext Context { get; } | |
/// <summary> | |
/// Additional Kestrel configuration (i.e. Startup class etc.) | |
/// </summary> | |
protected Action<IWebHostBuilder> ConfigureKestrel { get; } | |
/// <summary> | |
/// Specific endpoint name (likely from service manifest) otherwise empty | |
/// </summary> | |
protected string EndpointName { get; } = string.Empty; | |
/// <summary> | |
/// Host | |
/// </summary> | |
public IHost Host { get; set; } | |
/// <summary> | |
/// Hard-coded HTTP without TLS as protocol | |
/// </summary> | |
public EndpointProtocol[] EndpointProtocol { get; } = new [] { System.Fabric.Description.EndpointProtocol.Http, System.Fabric.Description.EndpointProtocol.Https }; // Remarks: Force HTTP without TLS | |
/// <summary> | |
/// HTTP endpoint description (i.e. protocol | port | etc.) | |
/// </summary> | |
public EndpointResourceDescription EndpointResourceDescription => Context | |
.CodePackageActivationContext | |
.GetEndpoints() | |
.Where(x => { | |
return string.IsNullOrEmpty(EndpointName) || EndpointName == x.Name; | |
}) | |
.FirstOrDefault(x => EndpointProtocol.Contains(x.Protocol)); | |
/// <summary> | |
/// Internal URL associated with the Endpoint description | |
/// </summary> | |
public string InternalUrl => EndpointResourceDescription is null ? "http://+:0" : $"{EndpointResourceDescription.Protocol}://+:{EndpointResourceDescription.Port}"; | |
/// <summary> | |
/// TODO: Flex configuration giving listener PartitionId and ReplicaId as url suffix | |
/// </summary> | |
protected string UrlSuffix { get; private set; } = string.Empty; | |
/// <summary> | |
/// Delegates to close | |
/// </summary> | |
public void Abort() | |
{ | |
CloseAsync(default!).Wait(); | |
} | |
/// <summary> | |
/// Closes communication listener to a terminal state | |
/// </summary> | |
/// <param name="cancellationToken"></param> | |
/// <returns></returns> | |
public async Task CloseAsync(CancellationToken cancellationToken) | |
{ | |
if (Host != null) | |
{ | |
await Host.StopAsync(cancellationToken); | |
Host.Dispose(); | |
} | |
} | |
/// <summary> | |
/// Open communication listener | |
/// </summary> | |
/// <param name="cancellationToken"></param> | |
/// <returns></returns> | |
public async Task<string> OpenAsync(CancellationToken cancellationToken) | |
{ | |
var url = InternalUrl.ToLower(); | |
var description = EndpointResourceDescription; | |
if (description is null) | |
{ | |
throw new ApplicationException($"Missing endpoint [protocol: {EndpointProtocol}, name: {EndpointName}]"); | |
} | |
Host = CreateHostBuilder(description, url).Build(); | |
await Host.StartAsync(cancellationToken); | |
if (url.Contains("://+:")) | |
{ | |
url = url.Replace("://+:", $"://{Context.PublishAddress}:"); | |
} | |
else if (url.Contains("://[::]:")) | |
{ | |
url = url.Replace("://[::]:", $"://{Context.PublishAddress}:"); | |
} | |
return url.TrimEnd(new[] { '/' }) + this.UrlSuffix; | |
} | |
/// <summary> | |
/// Create Kestrel | |
/// </summary> | |
/// <param name="description"></param> | |
/// <param name="url"></param> | |
/// <returns></returns> | |
public IHostBuilder CreateHostBuilder(EndpointResourceDescription description, string url) => Microsoft.Extensions.Hosting.Host | |
.CreateDefaultBuilder(new string[] { }) | |
.ConfigureWebHostDefaults(x => { | |
// See https://docs.microsoft.com/en-us/aspnet/core/grpc/troubleshoot?view=aspnetcore-3.0#call-insecure-grpc-services-with-net-core-client | |
x.ConfigureKestrel(options => | |
{ | |
var loggerFactory = options.ApplicationServices.GetService<ILoggerFactory>(); | |
options.Listen( | |
IPAddress.IPv6Any, | |
description.Port, | |
listenOptions => { | |
if (description.Protocol == System.Fabric.Description.EndpointProtocol.Http) | |
{ | |
listenOptions.Protocols = HttpProtocols.Http1; | |
} | |
if (description.Protocol == System.Fabric.Description.EndpointProtocol.Https) | |
{ | |
var certificate = GetFabricCertificate(loggerFactory); | |
listenOptions.Protocols = HttpProtocols.Http2; | |
if (certificate != null) { | |
listenOptions.UseHttps(certificate); | |
} | |
} | |
} | |
); | |
}); | |
x.UseUrls(url); | |
ConfigureKestrel(x); | |
}); | |
public X509Certificate2 GetFabricCertificate(ILoggerFactory loggerFactory) | |
{ | |
var logger = loggerFactory.CreateLogger<LocalKestrelCommunicationListener>(); | |
using var storage = new X509Store(StoreName.My, StoreLocation.LocalMachine); | |
try | |
{ | |
storage.Open(OpenFlags.ReadOnly); | |
var certificates = storage | |
.Certificates | |
.OfType<X509Certificate2>() | |
.Where(x => x.GetNameInfo(X509NameType.SimpleName, false).EndsWith(".se")); // TODO: Hard coded | |
foreach (var certificate in certificates) | |
{ | |
logger.LogInformation($"Certificate [subject: {certificate.Subject}, thumbprint: {certificate.Thumbprint}, issuer: {certificate.Issuer}, friendly name: {certificate.FriendlyName}, version: {certificate.Version}]"); | |
} | |
return certificates.LastOrDefault(); // TODO: Last always primary cert | |
} finally | |
{ | |
storage.Close(); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.ServiceFabric.Services.Communication.Runtime; | |
using Microsoft.ServiceFabric.Services.Runtime; | |
using System.Collections.Generic; | |
using System.Fabric; | |
using System.Fabric.Description; | |
using System.Linq; | |
namespace Gary.Service | |
{ | |
internal sealed class PublicApiService : StatelessService | |
{ | |
public PublicApiService( | |
StatelessServiceContext context | |
) : base(context) | |
{ | |
} | |
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() | |
{ | |
var endpoints = Context | |
.CodePackageActivationContext | |
.GetEndpoints() | |
.Where(endpoint => endpoint.Protocol == EndpointProtocol.Http || endpoint.Protocol == EndpointProtocol.Https) | |
.Select(endpoint => endpoint.Name); | |
return endpoints.Select(endpoint => new ServiceInstanceListener(context => | |
new LocalKestrelCommunicationListener( | |
context, | |
(builder) => builder | |
.ConfigureAppConfiguration(ConfigurationManager.BuildConfigurationAction) | |
.UseStartup<Startup>(), | |
endpoint | |
), | |
endpoint | |
)); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<ServiceManifest Name="Gary.ServicePkg" | |
Version="1.0.0" | |
xmlns="http://schemas.microsoft.com/2011/01/fabric" | |
xmlns:xsd="http://www.w3.org/2001/XMLSchema" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |
<ServiceTypes> | |
<!-- This is the name of your ServiceType. | |
This name must match the string used in RegisterServiceType call in Program.cs. --> | |
<StatelessServiceType ServiceTypeName="Gary.ServiceType" /> | |
</ServiceTypes> | |
<!-- Code package is your service executable. --> | |
<CodePackage Name="Code" Version="1.0.0"> | |
<EntryPoint> | |
<ExeHost> | |
<Program>Biometria.Produktion.PublicApi.Service.exe</Program> | |
<WorkingFolder>CodePackage</WorkingFolder> | |
</ExeHost> | |
</EntryPoint> | |
<EnvironmentVariables> | |
<EnvironmentVariable Name="ASPNETCORE_ENVIRONMENT" Value=""/> | |
<EnvironmentVariable Name="AzureServicesAuthConnectionString" Value=""/> | |
</EnvironmentVariables> | |
</CodePackage> | |
<!-- Config package is the contents of the Config directoy under PackageRoot that contains an | |
independently-updateable and versioned set of custom configuration settings for your service. --> | |
<ConfigPackage Name="Config" Version="1.0.0" /> | |
<Resources> | |
<Endpoints> | |
<!-- This endpoint is used by the communication listener to obtain the port on which to | |
listen. Please note that if your service is partitioned, this port is shared with | |
replicas of different partitions that are placed in your code. --> | |
<Endpoint Protocol="http" Name="KestrelEndpoint" Type="Input" Port="8401" /> | |
<Endpoint Protocol="https" Name="KestrelEndpointHttps" Type="Input" Port="443" /> | |
</Endpoints> | |
</Resources> | |
</ServiceManifest> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment