From f557fc30a6ef5536c05accaa914d037ed8da70a7 Mon Sep 17 00:00:00 2001 From: Mike Yagley Date: Thu, 11 Oct 2018 16:36:49 -0700 Subject: [PATCH] Add server certificate renewal for Edge Hub (#412) * Add server certificate renewal to Edge Hub * Add log line for no cert renewal * Remove percentage * Fix datetime subtraction * Fix string formatting * Add buffer for renewal * Address review comments * Update log line --- .../CertificateRenewal.cs | 61 +++++++++++++++++++ .../EdgeHubCertificates.cs | 4 +- .../Program.cs | 8 ++- .../ShutdownHandler.cs | 6 +- 4 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/CertificateRenewal.cs diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/CertificateRenewal.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/CertificateRenewal.cs new file mode 100644 index 00000000000..5c0d11aeb15 --- /dev/null +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/CertificateRenewal.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.Azure.Devices.Edge.Hub.Service +{ + using System; + using System.Threading; + using Microsoft.Azure.Devices.Edge.Util; + using Microsoft.Extensions.Logging; + + public class CertificateRenewal : IDisposable + { + readonly static TimeSpan TimeBuffer = TimeSpan.FromMinutes(5); + readonly CancellationTokenSource cts; + + /// + /// This cancellation token will expire when certificate renewal is required. + /// + public CancellationToken Token => this.cts.Token; + + public CertificateRenewal(EdgeHubCertificates certificates, ILogger logger) + { + Preconditions.CheckNotNull(certificates, nameof(certificates)); + Preconditions.CheckNotNull(logger, nameof(logger)); + + TimeSpan timeToExpire = certificates.ServerCertificate.NotAfter - DateTime.UtcNow; + if (timeToExpire > TimeBuffer) + { + var renewAfter = timeToExpire - TimeBuffer; + logger.LogInformation("Scheduling server certificate renewal for {0}.", DateTime.UtcNow.Add(renewAfter).ToString("o")); + this.cts = new CancellationTokenSource(renewAfter); + this.cts.Token.Register(l => ((ILogger)l).LogInformation("Restarting process to perform server certificate renewal."), logger); + } + else + { + this.cts = new CancellationTokenSource(); + logger.LogWarning("Server certificate is expired ({0}). Not scheduling renewal.", timeToExpire.ToString("c")); + } + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + try + { + this.cts.Dispose(); + } + catch (OperationCanceledException) + { + // ignore by design + } + } + } + } +} diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/EdgeHubCertificates.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/EdgeHubCertificates.cs index 84f7659a07b..d57d0f98701 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/EdgeHubCertificates.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/EdgeHubCertificates.cs @@ -20,8 +20,8 @@ public class EdgeHubCertificates EdgeHubCertificates(X509Certificate2 serverCertificate, IList certificateChain) { - this.ServerCertificate = serverCertificate; - this.CertificateChain = certificateChain; + this.ServerCertificate = Preconditions.CheckNotNull(serverCertificate, nameof(serverCertificate)); + this.CertificateChain = Preconditions.CheckNotNull(certificateChain, nameof(certificateChain)); } public static async Task LoadAsync(IConfigurationRoot configuration) diff --git a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Program.cs b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Program.cs index 0cf90feea84..75898280cf0 100644 --- a/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Program.cs +++ b/edge-hub/src/Microsoft.Azure.Devices.Edge.Hub.Service/Program.cs @@ -56,6 +56,8 @@ static async Task MainAsync(IConfigurationRoot configuration) LogLogo(logger); LogVersionInfo(logger); + logger.LogInformation("Loaded server certificate with expiration date of {0}", certificates.ServerCertificate.NotAfter.ToString("o")); + // EdgeHub and CloudConnectionProvider have a circular dependency. So need to Bind the EdgeHub to the CloudConnectionProvider. IEdgeHub edgeHub = await container.Resolve>(); ICloudConnectionProvider cloudConnectionProvider = await container.Resolve>(); @@ -90,14 +92,18 @@ static async Task MainAsync(IConfigurationRoot configuration) Metrics.BuildMetricsCollector(configuration); using (IProtocolHead protocolHead = await GetEdgeHubProtocolHeadAsync(logger, configuration, container, hosting).ConfigureAwait(false)) + using (var renewal = new CertificateRenewal(certificates, logger)) { await protocolHead.StartAsync(); - await cts.Token.WhenCanceled(); + await Task.WhenAny(cts.Token.WhenCanceled(), renewal.Token.WhenCanceled()); + logger.LogInformation("Stopping the protocol heads..."); await Task.WhenAny(protocolHead.CloseAsync(CancellationToken.None), Task.Delay(TimeSpan.FromSeconds(10), CancellationToken.None)); + logger.LogInformation("Protocol heads stopped."); } completed.Set(); handler.ForEach(h => GC.KeepAlive(h)); + logger.LogInformation("Shutdown complete."); return 0; } diff --git a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ShutdownHandler.cs b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ShutdownHandler.cs index 6a2126bcde4..f4efe07e26a 100644 --- a/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ShutdownHandler.cs +++ b/edge-util/src/Microsoft.Azure.Devices.Edge.Util/ShutdownHandler.cs @@ -11,8 +11,8 @@ public static class ShutdownHandler { /// /// Here are some references which were used for this code - - /// https://stackoverflow.com/questions/40742192/how-to-do-gracefully-shutdown-on-dotnet-with-docker/43813871 - /// https://msdn.microsoft.com/en-us/library/system.gc.keepalive(v=vs.110).aspx + /// https://stackoverflow.com/questions/40742192/how-to-do-gracefully-shutdown-on-dotnet-with-docker/43813871 + /// https://msdn.microsoft.com/en-us/library/system.gc.keepalive(v=vs.110).aspx /// public static (CancellationTokenSource cts, ManualResetEventSlim doneSignal, Option handler) Init(TimeSpan shutdownWaitPeriod, ILogger logger) @@ -61,7 +61,7 @@ void CancelProgram() } /// - /// This is the recommended way to handle shutdown of windows containers. References - + /// This is the recommended way to handle shutdown of windows containers. References - /// https://github.com/moby/moby/issues/25982 /// https://gist.github.com/darstahl/fbb80c265dcfd1b327aabcc0f3554e56 ///