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);
        }
Ejemplo n.º 2
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);
        }
        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);
        }
        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);
        }
Ejemplo n.º 5
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);
        }
Ejemplo n.º 6
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);
            }
        }