public async Task ValidCertificateShouldNotRequestNewCertificateAndAlsoSkipResourceUpdate() { var config = TestHelper.LoadConfig("config"); var auth = new Mock <IAuthenticationService>(); var certBuilder = new Mock <ICertificateBuilder>(); var ctx = new Mock <IAcmeContext>(); var orderContext = new Mock <IOrderContext>(); var authContext = new AuthenticationContext(ctx.Object, config.Acme); var parser = new Mock <IRenewalOptionParser>(); var log = new Mock <ILogger>(); IRenewalService service = new RenewalService(auth.Object, parser.Object, certBuilder.Object, log.Object); var certStore = new Mock <ICertificateStore>(); var validCert = new Mock <ICertificate>(); validCert.SetupGet(x => x.Expires) .Returns(DateTime.Now.AddDays(config.Acme.RenewXDaysBeforeExpiry + 1)); // check if outdated (no, found and valid) parser.Setup(x => x.ParseCertificateStore(config.Certificates[0])) .Returns(certStore.Object); certStore.Setup(x => x.GetCertificateAsync(CancellationToken.None)) .Returns(Task.FromResult(validCert.Object)); // actual run - should skip everything as cert is still valid (and assumed to be deployed already) var r = await service.RenewCertificateAsync(config.Acme, config.Certificates[0], CancellationToken.None); r.Should().Be(RenewalResult.NoChange); certStore.Verify(x => x.UploadAsync(It.IsAny <byte[]>(), It.IsAny <string>(), It.IsAny <string[]>(), It.IsAny <CancellationToken>()), Times.Never); }
public static int Main(string[] args) { Log.PreInitialize(); FixCosturaPath(); Options options; if (!TryParseOptions(args, out options)) { return(-1); } AcmeClientService acmeClient = new AcmeClientService(options); FirewallService firewallService = new FirewallService(); IisService iisService = new IisService(options, firewallService); AcmeCertificateService acmeCertificateService = new AcmeCertificateService(options, acmeClient, iisService); RenewalService renewalService = new RenewalService(options); CertificateStoreService certificateStoreService = new CertificateStoreService(options, iisService); Program program = new Program(options, acmeClient, iisService, acmeCertificateService, renewalService, certificateStoreService, firewallService); program.Execute(); return(0); }
public async Task NewCertificateShouldUpdateTargetResource() { var config = TestHelper.LoadConfig("config"); var auth = new Mock <IAuthenticationService>(); var certBuilder = new Mock <ICertificateBuilder>(); var ctx = new Mock <IAcmeContext>(); var orderContext = new Mock <IOrderContext>(); var authContext = new AuthenticationContext(ctx.Object, config.Acme); var parser = new Mock <IRenewalOptionParser>(); var log = new Mock <ILogger <RenewalService> >(); IRenewalService service = new RenewalService(auth.Object, parser.Object, certBuilder.Object, log.Object); var certStore = new Mock <ICertificateStore>(); var challengeResponder = new Mock <IChallengeResponder>(); var challengeContext = new Mock <IChallengeContext>(); var challenge = new Challenge { Status = ChallengeStatus.Valid }; var fakeChain = new CertificateChain("fakeChain"); var pfxBytes = new byte[] { 0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF }; const string fakePassword = "******"; var targetResource = new Mock <ITargetResource>(); var cert = new Mock <ICertificate>(); // check if outdated (yes, not found) parser.Setup(x => x.ParseCertificateStore(config.Certificates[0])) .Returns(certStore.Object); // let's encrypt challenge auth.Setup(x => x.AuthenticateAsync(config.Acme, CancellationToken.None)) .Returns(Task.FromResult(authContext)); ctx.Setup(x => x.NewOrder(config.Certificates[0].HostNames, null, null)) .Returns(Task.FromResult(orderContext.Object)); parser.Setup(x => x.ParseChallengeResponderAsync(config.Certificates[0], CancellationToken.None)) .Returns(Task.FromResult(challengeResponder.Object)); challengeResponder.Setup(x => x.InitiateChallengesAsync(orderContext.Object, CancellationToken.None)) .Returns(Task.FromResult(new[] { challengeContext.Object })); challengeContext.Setup(x => x.Resource()) .Returns(Task.FromResult(challenge)); // save cert certBuilder.Setup(x => x.BuildCertificateAsync(orderContext.Object, config.Certificates[0], CancellationToken.None)) .Returns(Task.FromResult((pfxBytes, fakePassword))); certStore.Setup(x => x.UploadAsync(pfxBytes, fakePassword, config.Certificates[0].HostNames, CancellationToken.None)) .Returns(Task.FromResult(cert.Object)); // update azure resource parser.Setup(x => x.ParseTargetResource(config.Certificates[0])) .Returns(targetResource.Object); // actual run - must run through all steps as no existing cert is found var r = await service.RenewCertificateAsync(config.Acme, config.Certificates[0], CancellationToken.None); r.Should().Be(RenewalResult.Success); certStore.Verify(x => x.UploadAsync(pfxBytes, fakePassword, config.Certificates[0].HostNames, CancellationToken.None)); targetResource.Verify(x => x.UpdateAsync(cert.Object, CancellationToken.None), Times.Once); }
private static async Task CheckDomainsForValidCertificateAsync(ILogger log, CancellationToken cancellationToken, ExecutionContext executionContext) { // internal storage (used for letsencrypt account metadata) IStorageProvider storageProvider = new AzureBlobStorageProvider(Environment.GetEnvironmentVariable("AzureWebJobsStorage"), "letsencrypt"); IConfigurationProcessor processor = new ConfigurationProcessor(); var configurations = await AutoRenewal.LoadConfigFilesAsync(storageProvider, processor, log, cancellationToken, executionContext); IAuthenticationService authenticationService = new AuthenticationService(storageProvider); var az = new AzureHelper(); var tokenProvider = new AzureServiceTokenProvider(); var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback)); var storageFactory = new StorageFactory(az); var renewalOptionsParser = new RenewalOptionParser(az, keyVaultClient, storageFactory, log); var certificateBuilder = new CertificateBuilder(); IRenewalService renewalService = new RenewalService(authenticationService, renewalOptionsParser, certificateBuilder, log); var errors = new List <Exception>(); var httpClient = new HttpClient(); foreach ((var name, var config) in configurations) { using (log.BeginScope($"Checking certificates from {name}")) { foreach (var cert in config.Certificates) { var hostNames = string.Join(";", cert.HostNames); try { // check each domain to verify HTTPS certificate is valid var request = WebRequest.CreateHttp($"https://{cert.HostNames.First()}"); request.ServerCertificateValidationCallback += ValidateTestServerCertificate; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { } } catch (Exception e) { log.LogError(e, $"Certificate check failed for: {hostNames}!"); errors.Add(e); continue; } log.LogInformation($"Certificate for {hostNames} looks valid"); } } } if (!configurations.Any()) { log.LogWarning("No configurations where processed, refere to the sample on how to set up configs!"); } if (errors.Any()) { throw new AggregateException("Failed to process all certificates", errors); } }
private Program(Options options, AcmeClientService acmeClient, IisService iisService, AcmeCertificateService acmeCertificateService, RenewalService renewalService, CertificateStoreService certificateStoreService, FirewallService firewallService) { _options = options; _acmeClient = acmeClient; _iisService = iisService; _acmeCertificateService = acmeCertificateService; _renewalService = renewalService; _certificateStoreService = certificateStoreService; _firewallService = firewallService; }
public async Task IfLatestCertificateIsNotInUseThenResourceShouldBeUpdated() { var config = TestHelper.LoadConfig("config"); var auth = new Mock <IAuthenticationService>(); var certBuilder = new Mock <ICertificateBuilder>(); var ctx = new Mock <IAcmeContext>(); var orderContext = new Mock <IOrderContext>(); var authContext = new AuthenticationContext(ctx.Object, config.Acme); var parser = new Mock <IRenewalOptionParser>(); var log = new Mock <ILogger <RenewalService> >(); IRenewalService service = new RenewalService(auth.Object, parser.Object, certBuilder.Object, log.Object); var certStore = new Mock <ICertificateStore>(); var targetResource = new Mock <ITargetResource>(); var validCert = new Mock <ICertificate>(); targetResource.SetupGet(x => x.SupportsCertificateCheck) .Returns(true); targetResource.Setup(x => x.IsUsingCertificateAsync(validCert.Object, It.IsAny <CancellationToken>())) .Returns(Task.FromResult(false)); validCert.SetupGet(x => x.Expires) .Returns(DateTime.Now.AddDays(config.Acme.RenewXDaysBeforeExpiry + 1)); validCert.SetupGet(x => x.HostNames) .Returns(config.Certificates[0].HostNames); // check if outdated (no, found and valid) parser.Setup(x => x.ParseCertificateStore(config.Certificates[0])) .Returns(certStore.Object); certStore.Setup(x => x.GetCertificateAsync(CancellationToken.None)) .Returns(Task.FromResult(validCert.Object)); // update azure resource parser.Setup(x => x.ParseTargetResource(config.Certificates[0])) .Returns(targetResource.Object); // actual run - must update resource with existing cert // as the cert is not yet in use var r = await service.RenewCertificateAsync(config.Acme, config.Certificates[0], CancellationToken.None); r.Should().Be(RenewalResult.Success); certStore.Verify(x => x.UploadAsync(It.IsAny <byte[]>(), It.IsAny <string>(), It.IsAny <string[]>(), It.IsAny <CancellationToken>()), Times.Never); targetResource.Verify(x => x.UpdateAsync(validCert.Object, CancellationToken.None), Times.Once); }
private static void Main(string[] args) { // Setup DI _container = AutofacBuilder.Global(args, _clientName, new PluginService()); // Basic services _log = _container.Resolve <ILogService>(); _optionsService = _container.Resolve <IOptionsService>(); _options = _optionsService.Options; if (_options == null) { return; } _input = _container.Resolve <IInputService>(); // .NET Framework check var dn = _container.Resolve <DotNetVersionService>(); if (!dn.Check()) { return; } // Show version information _input.ShowBanner(); // Advanced services _renewalService = _container.Resolve <RenewalService>(); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; // Main loop do { try { if (_options.Renew) { CheckRenewals(); CloseDefault(); } else if (!string.IsNullOrEmpty(_options.Plugin)) { if (_options.Cancel) { CancelRenewal(); } else { CreateNewCertificate(true); } CloseDefault(); } else { MainMenu(); } } catch (Exception e) { HandleException(e); Environment.ExitCode = e.HResult; } if (!_options.CloseOnFinish) { _options.Plugin = null; _options.Renew = false; _options.ForceRenewal = false; Environment.ExitCode = 0; } } while (!_options.CloseOnFinish); }
public async Task NewCertificateShouldBeIssuedWhenOverrideIsUsedAndDomainlistIsMatched() { var config = TestHelper.LoadConfig("config"); var auth = new Mock <IAuthenticationService>(); var certBuilder = new Mock <ICertificateBuilder>(); var ctx = new Mock <IAcmeContext>(); var orderContext = new Mock <IOrderContext>(); var authContext = new AuthenticationContext(ctx.Object, config.Acme); var parser = new Mock <IRenewalOptionParser>(); var log = new Mock <ILogger <RenewalService> >(); IRenewalService service = new RenewalService(auth.Object, parser.Object, certBuilder.Object, log.Object); var certStore = new Mock <ICertificateStore>(); var challengeResponder = new Mock <IChallengeResponder>(); var challengeContext = new Mock <IChallengeContext>(); var challenge = new Challenge { Status = ChallengeStatus.Valid }; var fakeChain = new CertificateChain("fakeChain"); var pfxBytes = new byte[] { 0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF }; const string fakePassword = "******"; var targetResource = new Mock <ITargetResource>(); var cert = new Mock <ICertificate>(); var validCert = new Mock <ICertificate>(); validCert.SetupGet(x => x.Expires) .Returns(DateTime.Now.AddDays(config.Acme.RenewXDaysBeforeExpiry + 1)); // check if outdated (no, found and valid) parser.Setup(x => x.ParseCertificateStore(config.Certificates[0])) .Returns(certStore.Object); certStore.Setup(x => x.GetCertificateAsync(CancellationToken.None)) .Returns(Task.FromResult(validCert.Object)); // let's encrypt challenge auth.Setup(x => x.AuthenticateAsync(config.Acme, CancellationToken.None)) .Returns(Task.FromResult(authContext)); ctx.Setup(x => x.NewOrder(config.Certificates[0].HostNames, null, null)) .Returns(Task.FromResult(orderContext.Object)); parser.Setup(x => x.ParseChallengeResponderAsync(config.Certificates[0], CancellationToken.None)) .Returns(Task.FromResult(challengeResponder.Object)); challengeResponder.Setup(x => x.InitiateChallengesAsync(orderContext.Object, CancellationToken.None)) .Returns(Task.FromResult(new[] { challengeContext.Object })); challengeContext.Setup(x => x.Resource()) .Returns(Task.FromResult(challenge)); // save cert certBuilder.Setup(x => x.BuildCertificateAsync(orderContext.Object, config.Certificates[0], CancellationToken.None)) .Returns(Task.FromResult((pfxBytes, fakePassword))); certStore.Setup(x => x.UploadAsync(pfxBytes, fakePassword, config.Certificates[0].HostNames, CancellationToken.None)) .Returns(Task.FromResult(cert.Object)); // update azure resource parser.Setup(x => x.ParseTargetResource(config.Certificates[0])) .Returns(targetResource.Object); // actual run - must run through everything despite valid cert due to override parameter config.Certificates[0].Overrides = new Overrides { ForceNewCertificates = true, DomainsToUpdate = new[] { "example.com" } }; var r = await service.RenewCertificateAsync(config.Acme, config.Certificates[0], CancellationToken.None); r.Should().Be(RenewalResult.Success); certStore.Verify(x => x.UploadAsync(pfxBytes, fakePassword, config.Certificates[0].HostNames, CancellationToken.None)); targetResource.Verify(x => x.UpdateAsync(cert.Object, CancellationToken.None), Times.Once); }
public async Task NoNewCertificateShouldBeIssuedWhenDomainListChangesOrder() { var config = TestHelper.LoadConfig("config"); var auth = new Mock <IAuthenticationService>(); var certBuilder = new Mock <ICertificateBuilder>(); var ctx = new Mock <IAcmeContext>(); var orderContext = new Mock <IOrderContext>(); var authContext = new AuthenticationContext(ctx.Object, config.Acme); var parser = new Mock <IRenewalOptionLoader>(); var log = new Mock <ILogger <RenewalService> >(); IRenewalService service = new RenewalService(auth.Object, parser.Object, certBuilder.Object, log.Object); var certStore = new Mock <ICertificateStore>(); var challengeResponder = new Mock <IChallengeResponder>(); var challengeContext = new Mock <IChallengeContext>(); var challenge = new Challenge { Status = ChallengeStatus.Valid }; var fakeChain = new CertificateChain("fakeChain"); var pfxBytes = new byte[] { 0xD, 0xE, 0xA, 0xD, 0xB, 0xE, 0xE, 0xF }; const string fakePassword = "******"; var targetResource = new Mock <ITargetResource>(); var cert = new Mock <ICertificate>(); var validCert = new Mock <ICertificate>(); validCert.SetupGet(x => x.Expires) .Returns(DateTime.Now.AddDays(config.Acme.RenewXDaysBeforeExpiry + 1)); validCert.SetupGet(x => x.HostNames) // config expects root first but this should not cause cert reissuance .Returns(new[] { "www.example.com", "example.com" }); // check if outdated (no, found and valid) parser.Setup(x => x.ParseCertificateStore(config.Certificates[0])) .Returns(certStore.Object); certStore.Setup(x => x.GetCertificateAsync(CancellationToken.None)) .Returns(Task.FromResult(validCert.Object)); // let's encrypt challenge auth.Setup(x => x.AuthenticateAsync(config.Acme, CancellationToken.None)) .Returns(Task.FromResult(authContext)); ctx.Setup(x => x.NewOrder(config.Certificates[0].HostNames, null, null)) .Returns(Task.FromResult(orderContext.Object)); parser.Setup(x => x.ParseChallengeResponderAsync(config.Certificates[0], CancellationToken.None)) .Returns(Task.FromResult(challengeResponder.Object)); challengeResponder.Setup(x => x.InitiateChallengesAsync(orderContext.Object, CancellationToken.None)) .Returns(Task.FromResult(new[] { challengeContext.Object })); challengeContext.Setup(x => x.Resource()) .Returns(Task.FromResult(challenge)); // save cert certBuilder.Setup(x => x.BuildCertificateAsync(orderContext.Object, config.Certificates[0], CancellationToken.None)) .Returns(Task.FromResult((pfxBytes, fakePassword))); certStore.Setup(x => x.UploadAsync(pfxBytes, fakePassword, config.Certificates[0].HostNames, CancellationToken.None)) .Returns(Task.FromResult(cert.Object)); // update azure resource parser.Setup(x => x.ParseTargetResource(config.Certificates[0])) .Returns(targetResource.Object); var r = await service.RenewCertificateAsync(config.Acme, config.Certificates[0], CancellationToken.None); r.Should().Be(RenewalResult.NoChange); certStore.Verify(x => x.UploadAsync(pfxBytes, fakePassword, config.Certificates[0].HostNames, CancellationToken.None), Times.Never); targetResource.Verify(x => x.UpdateAsync(cert.Object, CancellationToken.None), Times.Never); }
private static void Main(string[] args) { RegisterServices(); _log = Container.Resolve <ILogService>(); OptionsService = new OptionsService(_log, args); _options = OptionsService.Options; if (_options == null) { return; } Plugins = new PluginService(); ParseCentralSslStore(); CreateConfigPath(); // Basic services _settings = new Settings(_clientName, _configPath, _options.BaseUri); _input = new InputService(_options, _log, _settings.HostsPerPage()); // .NET Framework check if (!IsNET45) { _log.Error(".NET Framework 4.5 or higher is required for this app"); return; } // Show version information _input.ShowBanner(); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; do { try { // Configure AcmeClient var signer = new RS256Signer(); signer.Init(); _client = new AcmeClient(new Uri(_options.BaseUri), new AcmeServerDirectory(), signer); ConfigureAcmeClient(_client); _certificateService = new CertificateService(_options, _log, _client, _configPath); _certificateStoreService = new CertificateStoreService(_options, _log); _centralSslService = new CentralSslService(_options, _log, _certificateService); _renewalService = new RenewalService(_options, _log, _settings, _input, _clientName, _configPath); if (_options.Renew) { CheckRenewals(); CloseDefault(); } else if (!string.IsNullOrEmpty(_options.Plugin)) { CreateNewCertificateUnattended(); CloseDefault(); } else { MainMenu(); } } catch (Exception e) { HandleException(e); Environment.ExitCode = e.HResult; } if (!_options.CloseOnFinish) { _options.Plugin = null; _options.Renew = false; _options.ForceRenewal = false; Environment.ExitCode = 0; } } while (!_options.CloseOnFinish); }
private static async Task RenewAsync(Overrides overrides, ILogger log, CancellationToken cancellationToken, ExecutionContext executionContext) { // internal storage (used for letsencrypt account metadata) IStorageProvider storageProvider = new AzureBlobStorageProvider(Environment.GetEnvironmentVariable("AzureWebJobsStorage"), "letsencrypt"); IConfigurationProcessor processor = new ConfigurationProcessor(); var configurations = await LoadConfigFilesAsync(storageProvider, processor, log, cancellationToken, executionContext); IAuthenticationService authenticationService = new AuthenticationService(storageProvider); var az = new AzureHelper(); var tokenProvider = new AzureServiceTokenProvider(); var keyVaultClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback)); var storageFactory = new StorageFactory(az); var renewalOptionsParser = new RenewalOptionParser(az, keyVaultClient, storageFactory, log); var certificateBuilder = new CertificateBuilder(); IRenewalService renewalService = new RenewalService(authenticationService, renewalOptionsParser, certificateBuilder, log); var stopwatch = new Stopwatch(); // TODO: with lots of certificate renewals this could run into function timeout (10mins) // with 30 days to expiry (default setting) this isn't a big problem as next day all finished certs are skipped // user will only get email <= 14 days before expiry so acceptable for now var errors = new List <Exception>(); foreach ((var name, var config) in configurations) { using (log.BeginScope($"Working on certificates from {name}")) { foreach (var cert in config.Certificates) { stopwatch.Restart(); var hostNames = string.Join(";", cert.HostNames); cert.Overrides = overrides ?? Overrides.None; try { var result = await renewalService.RenewCertificateAsync(config.Acme, cert, cancellationToken); switch (result) { case RenewalResult.NoChange: log.LogInformation($"Certificate renewal skipped for: {hostNames} (no change required yet)"); break; case RenewalResult.Success: log.LogInformation($"Certificate renewal succeeded for: {hostNames}"); break; default: throw new ArgumentOutOfRangeException(result.ToString()); } } catch (Exception e) { log.LogError(e, $"Certificate renewal failed for: {hostNames}!"); errors.Add(e); } log.LogInformation($"Renewing certificates for {hostNames} took: {stopwatch.Elapsed}"); } } } if (!configurations.Any()) { log.LogWarning("No configurations where processed, refere to the sample on how to set up configs!"); } if (errors.Any()) { throw new AggregateException("Failed to process all certificates", errors); } }