public void ParsingChallengeResponderShouldWorkIfCallerHasMsiAccessToStorage()
        {
            var az      = new Mock <IAzureHelper>();
            var factory = new Mock <IStorageFactory>();
            var storage = new Mock <IStorageProvider>();

            // check is used by parser to verify MSI access
            storage.Setup(x => x.ExistsAsync(RenewalOptionParser.FileNameForPermissionCheck, It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(false));
            factory.Setup(x => x.FromMsiAsync("example", new StorageProperties().ContainerName, It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(storage.Object));

            var kvFactory = new Mock <IKeyVaultFactory>();
            var client    = new Mock <CertificateClient>();

            kvFactory.Setup(x => x.CreateCertificateClient("example"))
            .Returns(client.Object);
            var log = new Mock <ILoggerFactory>();
            IRenewalOptionParser parser = new RenewalOptionParser(
                az.Object,
                kvFactory.Object,
                factory.Object,
                new Mock <IAzureAppServiceClient>().Object,
                new Mock <IAzureCdnClient>().Object,
                log.Object);

            var cfg = TestHelper.LoadConfig("config");

            new Func <Task>(async() => _ = await parser.ParseChallengeResponderAsync(cfg.Certificates[0], CancellationToken.None)).Should().NotThrow();
        }
        public void ParsingChallengeResponderShouldFailIfCallerHasNoMsiAccessToStorageAndFallbacksAreNotAvailable()
        {
            var az      = new Mock <IAzureHelper>();
            var kv      = new Mock <IKeyVaultClient>();
            var factory = new Mock <IStorageFactory>();
            var storage = new Mock <IStorageProvider>();

            // check is used by parser to verify MSI access
            storage.Setup(x => x.ExistsAsync(RenewalOptionParser.FileNameForPermissionCheck, It.IsAny <CancellationToken>()))
            .Throws(new StorageException(new RequestResult
            {
                HttpStatusCode = (int)HttpStatusCode.Forbidden
            }, "Access denied, due to missing MSI permissions", null));
            // fallback is keyvault -> secret not found.
            kv.Setup(x => x.GetSecretWithHttpMessagesAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <Dictionary <string, List <string> > >(), It.IsAny <CancellationToken>()))
            .Throws(new KeyVaultErrorException("denied")
            {
                Response = new HttpResponseMessageWrapper(new HttpResponseMessage
                {
                    StatusCode = HttpStatusCode.NotFound
                }, "denied")
            });
            factory.Setup(x => x.FromMsiAsync("example", new StorageProperties().ContainerName, It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(storage.Object));

            var log = new Mock <ILogger>();
            IRenewalOptionParser parser = new RenewalOptionParser(az.Object, kv.Object, factory.Object, log.Object);

            var cfg = TestHelper.LoadConfig("config");

            new Func <Task>(async() => _ = await parser.ParseChallengeResponderAsync(cfg.Certificates[0], CancellationToken.None)).Should().Throw <InvalidOperationException>();

            kv.Verify(x => x.GetSecretWithHttpMessagesAsync("https://example.vault.azure.net", new StorageProperties().SecretName, "", null, CancellationToken.None), Times.Once);
        }
        public async Task ParsingChallengeResponderShouldSucceedIfCallerHasNoMsiAccessToConnectionStringFallbackIsAvailable()
        {
            var az      = new Mock <IAzureHelper>();
            var kv      = new Mock <IKeyVaultClient>();
            var factory = new Mock <IStorageFactory>();
            var storage = new Mock <IStorageProvider>();

            // check is used by parser to verify MSI access
            storage.Setup(x => x.ExistsAsync(RenewalOptionParser.FileNameForPermissionCheck, It.IsAny <CancellationToken>()))
            .Throws(new StorageException(new RequestResult
            {
                HttpStatusCode = (int)HttpStatusCode.Forbidden
            }, "Access denied, due to missing MSI permissions", null));
            // fallback is connectionString
            const string connectionString = "this will grant me access";

            factory.Setup(x => x.FromMsiAsync("example", new StorageProperties().ContainerName, It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(storage.Object));
            // fallback
            factory.Setup(x => x.FromConnectionString(connectionString, new StorageProperties().ContainerName))
            .Returns(storage.Object);

            var log = new Mock <ILogger>();
            IRenewalOptionParser parser = new RenewalOptionParser(az.Object, kv.Object, factory.Object, log.Object);

            var cfg = TestHelper.LoadConfig("config+connectionstring");
            var r   = await parser.ParseChallengeResponderAsync(cfg.Certificates[0], CancellationToken.None);

            // keyvault should not be tried if connectionstring is found
            kv.VerifyNoOtherCalls();
            factory.Verify(x => x.FromConnectionString(connectionString, new StorageProperties().ContainerName), Times.Once);
        }
示例#4
0
        public async Task LoadingConfigWithCustomStoragePathShouldUseIt()
        {
            IConfigurationProcessor processor = new ConfigurationProcessor();
            var content = File.ReadAllText("Files/config+custompath.json");
            var cfg     = processor.ValidateAndLoad(content);
            var cert    = cfg.Certificates[0];

            cert.HostNames.Should().BeEquivalentTo(new[]
            {
                "example.com", "www.example.com"
            });
            cert.ChallengeResponder.Should().NotBeNull();

            // fake grant MSI access
            var factory = new Mock <IStorageFactory>();
            var storage = new Mock <IStorageProvider>();

            storage.Setup(x => x.ExistsAsync(It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(true));
            factory.Setup(x => x.FromMsiAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(storage.Object));

            var parser = new RenewalOptionParser(
                new Mock <IAzureHelper>().Object,
                new Mock <IKeyVaultClient>().Object,
                factory.Object,
                new Mock <IAzureAppServiceClient>().Object,
                new Mock <IAzureCdnClient>().Object,
                new Mock <ILoggerFactory>().Object);
            var responder = await parser.ParseChallengeResponderAsync(cert, CancellationToken.None);

            var ctx = new Mock <IChallengeContext>();

            // Certes .Http() extension method internall filters for this type
            ctx.SetupGet(x => x.Type)
            .Returns("http-01");

            ctx.SetupGet(x => x.Token)
            .Returns("fileNAME");
            ctx.SetupGet(x => x.KeyAuthz)
            .Returns("$content");

            var auth = new Mock <IAuthorizationContext>();

            auth.Setup(x => x.Challenges())
            .Returns(Task.FromResult(new[] { ctx.Object }.AsEnumerable()));
            var order = new Mock <IOrderContext>();

            order.Setup(x => x.Authorizations())
            .Returns(Task.FromResult(new[] { auth.Object }.AsEnumerable()));
            _ = await responder.InitiateChallengesAsync(order.Object, CancellationToken.None);

            const string pathPrefix = "not/well-known/";

            storage.Verify(x => x.SetAsync(pathPrefix + "fileNAME", "$content", It.IsAny <CancellationToken>()), Times.Once);
        }
        public async Task ParsingChallengeResponderShouldSucceedIfCallerHasNoMsiAccessToStorageButKeyVaultFallbackIsAvailable()
        {
            var az      = new Mock <IAzureHelper>();
            var factory = new Mock <IStorageFactory>();
            var storage = new Mock <IStorageProvider>();

            // check is used by parser to verify MSI access
            storage.Setup(x => x.ExistsAsync(RenewalOptionParser.FileNameForPermissionCheck, It.IsAny <CancellationToken>()))
            .Throws(new RequestFailedException((int)HttpStatusCode.Forbidden, "Access denied, due to missing MSI permissions"));

            factory.Setup(x => x.FromMsiAsync("example", new StorageProperties().ContainerName, It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(storage.Object));

            var log = new Mock <ILoggerFactory>();

            log.Setup(x => x.CreateLogger(It.IsAny <string>()))
            .Returns(new Mock <ILogger>().Object);
            var kvFactory    = new Mock <IKeyVaultFactory>();
            var client       = new Mock <CertificateClient>();
            var secretClient = new Mock <SecretClient>();
            // fallback is keyvault
            const string connectionString = "this will grant me access";
            var          response         = new Mock <Response <KeyVaultSecret> >();

            response.SetupGet(x => x.Value)
            .Returns(new KeyVaultSecret(new StorageProperties().SecretName, connectionString));
            secretClient.Setup(x => x.GetSecretAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(response.Object);
            kvFactory.Setup(x => x.CreateSecretClient("example"))
            .Returns(secretClient.Object);
            kvFactory.Setup(x => x.CreateCertificateClient("example"))
            .Returns(client.Object);

            // fallback
            factory.Setup(x => x.FromConnectionString(connectionString, new StorageProperties().ContainerName))
            .Returns(storage.Object);
            IRenewalOptionParser parser = new RenewalOptionParser(
                az.Object,
                kvFactory.Object,
                factory.Object,
                new Mock <IAzureAppServiceClient>().Object,
                new Mock <IAzureCdnClient>().Object,
                log.Object);

            var cfg = TestHelper.LoadConfig("config");
            var r   = await parser.ParseChallengeResponderAsync(cfg.Certificates[0], CancellationToken.None);

            secretClient.Verify(x => x.GetSecretAsync(new StorageProperties().SecretName, null, CancellationToken.None), Times.Once);
            factory.Verify(x => x.FromConnectionString(connectionString, new StorageProperties().ContainerName), Times.Once);
        }
        public async Task ParsingChallengeResponderShouldSucceedIfCallerHasNoMsiAccessToStorageButKeyVaultFallbackIsAvailable()
        {
            var az      = new Mock <IAzureHelper>();
            var kv      = new Mock <IKeyVaultClient>();
            var factory = new Mock <IStorageFactory>();
            var storage = new Mock <IStorageProvider>();

            // check is used by parser to verify MSI access
            storage.Setup(x => x.ExistsAsync(RenewalOptionParser.FileNameForPermissionCheck, It.IsAny <CancellationToken>()))
            .Throws(new StorageException(new RequestResult
            {
                HttpStatusCode = (int)HttpStatusCode.Forbidden
            }, "Access denied, due to missing MSI permissions", null));
            // fallback is keyvault
            const string connectionString = "this will grant me access";

            kv.Setup(x => x.GetSecretWithHttpMessagesAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <Dictionary <string, List <string> > >(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(new AzureOperationResponse <SecretBundle>
            {
                Body = new SecretBundle(connectionString)
            }));

            factory.Setup(x => x.FromMsiAsync("example", new StorageProperties().ContainerName, It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(storage.Object));
            // fallback
            factory.Setup(x => x.FromConnectionString(connectionString, new StorageProperties().ContainerName))
            .Returns(storage.Object);

            var log = new Mock <ILoggerFactory>();

            log.Setup(x => x.CreateLogger(It.IsAny <string>()))
            .Returns(new Mock <ILogger>().Object);
            IRenewalOptionParser parser = new RenewalOptionParser(
                az.Object,
                kv.Object,
                factory.Object,
                new Mock <IAzureAppServiceClient>().Object,
                new Mock <IAzureCdnClient>().Object,
                log.Object);

            var cfg = TestHelper.LoadConfig("config");
            var r   = await parser.ParseChallengeResponderAsync(cfg.Certificates[0], CancellationToken.None);

            kv.Verify(x => x.GetSecretWithHttpMessagesAsync("https://example.vault.azure.net", new StorageProperties().SecretName, "", null, CancellationToken.None), Times.Once);
            factory.Verify(x => x.FromConnectionString(connectionString, new StorageProperties().ContainerName), Times.Once);
        }
        public void ParsingChallengeResponderShouldFailIfCallerHasNoMsiAccessToStorageAndFallbacksAreNotAvailable()
        {
            var az      = new Mock <IAzureHelper>();
            var factory = new Mock <IStorageFactory>();
            var storage = new Mock <IStorageProvider>();

            // check is used by parser to verify MSI access
            storage.Setup(x => x.ExistsAsync(RenewalOptionParser.FileNameForPermissionCheck, It.IsAny <CancellationToken>()))
            .Throws(new RequestFailedException((int)HttpStatusCode.Forbidden, "Access denied, due to missing MSI permissions"));

            factory.Setup(x => x.FromMsiAsync("example", new StorageProperties().ContainerName, It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(storage.Object));

            var log = new Mock <ILoggerFactory>();

            log.Setup(x => x.CreateLogger(It.IsAny <string>()))
            .Returns(new Mock <ILogger>().Object);
            var kvFactory    = new Mock <IKeyVaultFactory>();
            var client       = new Mock <CertificateClient>();
            var secretClient = new Mock <SecretClient>();

            // fallback is keyvault -> secret not found.
            secretClient.Setup(x => x.GetSecretAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .Throws(new RequestFailedException((int)HttpStatusCode.NotFound, "denied"));
            kvFactory.Setup(x => x.CreateSecretClient("example"))
            .Returns(secretClient.Object);
            kvFactory.Setup(x => x.CreateCertificateClient("example"))
            .Returns(client.Object);

            IRenewalOptionParser parser = new RenewalOptionParser(
                az.Object,
                kvFactory.Object,
                factory.Object,
                new Mock <IAzureAppServiceClient>().Object,
                new Mock <IAzureCdnClient>().Object,
                log.Object);

            var cfg = TestHelper.LoadConfig("config");

            new Func <Task>(async() => _ = await parser.ParseChallengeResponderAsync(cfg.Certificates[0], CancellationToken.None)).Should().Throw <InvalidOperationException>();

            secretClient.Verify(x => x.GetSecretAsync(new StorageProperties().SecretName, null, CancellationToken.None), Times.Once);
        }