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);
        }
Пример #2
0
        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);
        }
Пример #3
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);
        }
Пример #4
0
        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);
            }
        }
Пример #5
0
 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);
        }
Пример #7
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
        }
Пример #11
0
        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);
            }
        }