Skip to content

Commit

Permalink
Add server certificate renewal for Edge Hub (#412)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
myagley authored Oct 11, 2018
1 parent 669854e commit f557fc3
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// This cancellation token will expire when certificate renewal is required.
/// </summary>
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
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class EdgeHubCertificates

EdgeHubCertificates(X509Certificate2 serverCertificate, IList<X509Certificate2> 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<EdgeHubCertificates> LoadAsync(IConfigurationRoot configuration)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ static async Task<int> 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<Task<IEdgeHub>>();
ICloudConnectionProvider cloudConnectionProvider = await container.Resolve<Task<ICloudConnectionProvider>>();
Expand Down Expand Up @@ -90,14 +92,18 @@ static async Task<int> 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public static class ShutdownHandler
{
/// <summary>
/// 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
/// </summary>
public static (CancellationTokenSource cts, ManualResetEventSlim doneSignal, Option<object> handler)
Init(TimeSpan shutdownWaitPeriod, ILogger logger)
Expand Down Expand Up @@ -61,7 +61,7 @@ void CancelProgram()
}

/// <summary>
/// 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
/// </summary>
Expand Down

0 comments on commit f557fc3

Please sign in to comment.