public async Task GetHostSecrets_WhenNonDecryptedHostSecrets_SavesAndRefreshes()
        {
            using (var directory = new TempDirectory())
            {
                string expectedTraceMessage = Resources.TraceNonDecryptedHostSecretRefresh;
                string hostSecretsJson      =
                    @"{
    'masterKey': {
        'name': 'master',
        'value': 'cryptoError',
        'encrypted': true
    },
    'functionKeys': [],
    'systemKeys': []
}";
                File.WriteAllText(Path.Combine(directory.Path, ScriptConstants.HostMetadataFileName), hostSecretsJson);
                Mock <IKeyValueConverterFactory> mockValueConverterFactory = GetConverterFactoryMock(true, false);
                HostSecretsInfo    hostSecrets;
                ISecretsRepository repository = new FileSystemSecretsRepository(directory.Path);

                using (var secretManager = new SecretManager(repository, mockValueConverterFactory.Object, null))
                {
                    hostSecrets = await secretManager.GetHostSecretsAsync();
                }

                Assert.NotNull(hostSecrets);
                Assert.Equal(hostSecrets.MasterKey, "cryptoError");
                var result = JsonConvert.DeserializeObject <HostSecrets>(File.ReadAllText(Path.Combine(directory.Path, ScriptConstants.HostMetadataFileName)));
                Assert.Equal(result.MasterKey.Value, "!cryptoError");
                Assert.Equal(1, Directory.GetFiles(directory.Path, $"host.{ScriptConstants.Snapshot}*").Length);
            }
        }
        public async Task GetHostSecrets_UpdatesStaleSecrets()
        {
            using (var directory = new TempDirectory())
            {
                string expectedTraceMessage = Resources.TraceStaleHostSecretRefresh;
                string hostSecretsJson      =
                    @"{
    'masterKey': {
        'name': 'master',
        'value': '1234',
        'encrypted': false
    },
    'functionKeys': [
        {
            'name': 'Key1',
            'value': 'HostValue1',
            'encrypted': false
        },
        {
            'name': 'Key3',
            'value': 'HostValue3',
            'encrypted': false
        }
    ],
    'systemKeys': [
        {
            'name': 'SystemKey1',
            'value': 'SystemHostValue1',
            'encrypted': false
        },
        {
            'name': 'SystemKey2',
            'value': 'SystemHostValue2',
            'encrypted': false
        }
    ]
}";
                File.WriteAllText(Path.Combine(directory.Path, ScriptConstants.HostMetadataFileName), hostSecretsJson);

                Mock <IKeyValueConverterFactory> mockValueConverterFactory = GetConverterFactoryMock();

                HostSecretsInfo    hostSecrets;
                ISecretsRepository repository = new FileSystemSecretsRepository(directory.Path);
                using (var secretManager = new SecretManager(repository, mockValueConverterFactory.Object, NullLogger.Instance))
                {
                    hostSecrets = await secretManager.GetHostSecretsAsync();
                }

                // Read the persisted content
                var  result = JsonConvert.DeserializeObject <HostSecrets>(File.ReadAllText(Path.Combine(directory.Path, ScriptConstants.HostMetadataFileName)));
                bool functionSecretsConverted = hostSecrets.FunctionKeys.Values.Zip(result.FunctionKeys, (r1, r2) => string.Equals("!" + r1, r2.Value)).All(r => r);
                bool systemSecretsConverted   = hostSecrets.SystemKeys.Values.Zip(result.SystemKeys, (r1, r2) => string.Equals("!" + r1, r2.Value)).All(r => r);

                Assert.Equal(2, result.FunctionKeys.Count);
                Assert.Equal(2, result.SystemKeys.Count);
                Assert.Equal("!" + hostSecrets.MasterKey, result.MasterKey.Value);
                Assert.True(functionSecretsConverted, "Function secrets were not persisted");
                Assert.True(systemSecretsConverted, "System secrets were not persisted");
            }
        }
        public async Task GetHostSecrets_WhenNoHostSecretFileExists_GeneratesSecretsAndPersistsFiles()
        {
            using (var directory = new TempDirectory())
            {
                string expectedTraceMessage = Resources.TraceHostSecretGeneration;
                Mock <IKeyValueConverterFactory> mockValueConverterFactory = GetConverterFactoryMock(false, false);

                HostSecretsInfo    hostSecrets;
                ISecretsRepository repository = new FileSystemSecretsRepository(directory.Path);
                using (var secretManager = new SecretManager(repository, mockValueConverterFactory.Object, NullLogger.Instance))
                {
                    hostSecrets = await secretManager.GetHostSecretsAsync();
                }

                string      secretsJson      = File.ReadAllText(Path.Combine(directory.Path, ScriptConstants.HostMetadataFileName));
                HostSecrets persistedSecrets = ScriptSecretSerializer.DeserializeSecrets <HostSecrets>(secretsJson);

                Assert.NotNull(hostSecrets);
                Assert.NotNull(persistedSecrets);
                Assert.Equal(1, hostSecrets.FunctionKeys.Count);
                Assert.NotNull(hostSecrets.MasterKey);
                Assert.NotNull(hostSecrets.SystemKeys);
                Assert.Equal(0, hostSecrets.SystemKeys.Count);
                Assert.Equal(persistedSecrets.MasterKey.Value, hostSecrets.MasterKey);
                Assert.Equal(persistedSecrets.FunctionKeys.First().Value, hostSecrets.FunctionKeys.First().Value);
            }
        }
        public async Task AddOrUpdateFunctionSecret_ClearsCache_WhenHostSystemSecretAdded()
        {
            using (var directory = new TempDirectory())
            {
                CreateTestSecrets(directory.Path);

                Mock <IKeyValueConverterFactory> mockValueConverterFactory = GetConverterFactoryMock(false);
                KeyOperationResult result;
                ISecretsRepository repository = new FileSystemSecretsRepository(directory.Path);
                using (var secretManager = new SecretManager(repository, mockValueConverterFactory.Object, _logger, new TestMetricsLogger(), _hostNameProvider))
                {
                    var hostKeys = await secretManager.GetHostSecretsAsync();

                    Assert.Equal(2, hostKeys.SystemKeys.Count);

                    // add a new key
                    result = await secretManager.AddOrUpdateFunctionSecretAsync("host-system-3", "123", HostKeyScopes.SystemKeys, ScriptSecretsType.Host);
                }

                string secretsJson      = File.ReadAllText(Path.Combine(directory.Path, "host.json"));
                var    persistedSecrets = ScriptSecretSerializer.DeserializeSecrets <HostSecrets>(secretsJson);

                Assert.Equal(OperationResult.Created, result.Result);
                Assert.Equal(result.Secret, "123");

                var logs = _loggerProvider.GetAllLogMessages();
                Assert.Equal(1, logs.Count(p => p.FormattedMessage == "Host keys change detected. Clearing cache."));
                Assert.Equal(1, logs.Count(p => p.FormattedMessage == "Host secret 'host-system-3' for 'systemkeys' Created."));
            }
        }
        public async Task GetHostSecrets_AddMetrics()
        {
            using (var directory = new TempDirectory())
            {
                string expectedTraceMessage = Resources.TraceNonDecryptedHostSecretRefresh;
                string hostSecretsJson      =
                    @"{
    'masterKey': {
        'name': 'master',
        'value': 'cryptoError',
        'encrypted': true
    },
    'functionKeys': [],
    'systemKeys': []
}";
                File.WriteAllText(Path.Combine(directory.Path, ScriptConstants.HostMetadataFileName), hostSecretsJson);
                Mock <IKeyValueConverterFactory> mockValueConverterFactory = GetConverterFactoryMock(true, false);
                HostSecretsInfo    hostSecrets;
                ISecretsRepository repository    = new FileSystemSecretsRepository(directory.Path);
                TestMetricsLogger  metricsLogger = new TestMetricsLogger();

                using (var secretManager = new SecretManager(repository, mockValueConverterFactory.Object, null, metricsLogger, _hostNameProvider))
                {
                    hostSecrets = await secretManager.GetHostSecretsAsync();
                }

                string eventName = string.Format(MetricEventNames.SecretManagerGetHostSecrets, repository.GetType().Name.ToLower());
                metricsLogger.EventsBegan.Single(e => string.Equals(e, eventName));
                metricsLogger.EventsEnded.Single(e => string.Equals(e.ToString(), eventName));
            }
        }
        public async Task <HostStatus> GetHostStatusAsync()
        {
            HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync();

            string uri = $"admin/host/status?code={secrets.MasterKey}";
            HttpResponseMessage response = await HttpClient.GetAsync(uri);

            response.EnsureSuccessStatusCode();
            return(await response.Content.ReadAsAsync <HostStatus>());
        }
Ejemplo n.º 7
0
        private async Task <bool> IsHostRunning(HttpClient client)
        {
            HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync();

            // Workaround for https://github.com/Azure/azure-functions-host/issues/2397 as the base URL
            // doesn't currently start the host.
            // Note: the master key "1234" is from the TestSecretManager.
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"/admin/functions/dummyName/status?code={secrets.MasterKey}"))
            {
                using (HttpResponseMessage response = await client.SendAsync(request))
                {
                    return(response.StatusCode == HttpStatusCode.NoContent || response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.NotFound);
                }
            }
        }
        public async Task BeginFunctionAsync(string functionName, JToken payload)
        {
            JObject wrappedPayload = new JObject
            {
                { "input", payload.ToString() }
            };

            HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync();

            string             uri     = $"admin/functions/{functionName}?code={secrets.MasterKey}";
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);

            request.Content = new StringContent(wrappedPayload.ToString(), Encoding.UTF8, "application/json");
            HttpResponseMessage response = await HttpClient.SendAsync(request);

            response.EnsureSuccessStatusCode();
        }
        private async Task <bool> IsHostStarted(HttpClient client)
        {
            //HostStatus status = await GetHostStatusAsync();
            //return status.State == $"{ScriptHostState.Running}" || status.State == $"{ScriptHostState.Error}";

            HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync();

            // Workaround for https://github.com/Azure/azure-functions-host/issues/2397 as the base URL
            // doesn't currently start the host.
            // Note: the master key "1234" is from the TestSecretManager.
            using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, $"/admin/functions/dummyName/status?code={secrets.MasterKey}"))
            {
                using (HttpResponseMessage response = await client.SendAsync(request))
                {
                    //return response.StatusCode == HttpStatusCode.NoContent || response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.NotFound;
                    // Throw away this response.
                }
            }

            return(_hostService.State == ScriptHostState.Running || _hostService.State == ScriptHostState.Error);
        }
        private SecretManager CreateSecretManager(string secretsPath, ILogger logger = null, IMetricsLogger metricsLogger = null, IKeyValueConverterFactory keyConverterFactory = null, bool createHostSecretsIfMissing = false, bool simulateWriteConversion = true, bool setStaleValue = true)
        {
            logger        = logger ?? _logger;
            metricsLogger = metricsLogger ?? new TestMetricsLogger();

            if (keyConverterFactory == null)
            {
                Mock <IKeyValueConverterFactory> mockValueConverterFactory = GetConverterFactoryMock(simulateWriteConversion, setStaleValue);
                keyConverterFactory = mockValueConverterFactory.Object;
            }

            ISecretsRepository repository = new FileSystemSecretsRepository(secretsPath);
            var secretManager             = new SecretManager(repository, keyConverterFactory, logger, metricsLogger, _hostNameProvider, _startupContextProvider);

            if (createHostSecretsIfMissing)
            {
                secretManager.GetHostSecretsAsync().GetAwaiter().GetResult();
            }

            return(secretManager);
        }
Ejemplo n.º 11
0
        public async Task InstallBindingExtension(string packageName, string packageVersion)
        {
            HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync();

            string             uri     = $"admin/host/extensions?code={secrets.MasterKey}";
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);

            string payload = new JObject
            {
                { "id", packageName },
                { "version", packageVersion }
            }.ToString(Newtonsoft.Json.Formatting.None);

            request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
            var response = await HttpClient.SendAsync(request);

            var    jobStatusUri = response.Headers.Location;
            string status       = null;

            do
            {
                await Task.Delay(500);

                response = await CheckExtensionInstallStatus(jobStatusUri);

                var jobStatus = await response.Content.ReadAsAsync <JObject>();

                status = jobStatus["status"].ToString();
            } while (status == "Started");

            if (status != "Succeeded")
            {
                throw new InvalidOperationException("Failed to install extension.");
            }

            // TODO: Find a better way to ensure the site has restarted.
            await Task.Delay(3000);
        }
        public async Task GetHostSecretsAsync_WaitsForNewSecrets()
        {
            using (var directory = new TempDirectory())
            {
                string hostSecretsJson = @"{
    'masterKey': {
        'name': 'master',
        'value': '1234',
        'encrypted': false
    },
    'functionKeys': [],
    'systemKeys': []
}";
                string filePath        = Path.Combine(directory.Path, ScriptConstants.HostMetadataFileName);
                File.WriteAllText(filePath, hostSecretsJson);

                HostSecretsInfo    hostSecrets = null;
                ISecretsRepository repository  = new FileSystemSecretsRepository(directory.Path);

                using (var secretManager = new SecretManager(_settingsManager, repository, null))
                {
                    await Task.WhenAll(
                        Task.Run(async() =>
                    {
                        // Lock the file
                        using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.None))
                        {
                            await Task.Delay(500);
                        }
                    }),
                        Task.Run(async() =>
                    {
                        await Task.Delay(100);
                        hostSecrets = await secretManager.GetHostSecretsAsync();
                    }));

                    Assert.Equal(hostSecrets.MasterKey, "1234");
                }

                using (var secretManager = new SecretManager(_settingsManager, repository, null))
                {
                    await Assert.ThrowsAsync <IOException>(async() =>
                    {
                        await Task.WhenAll(
                            Task.Run(async() =>
                        {
                            // Lock the file
                            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.None))
                            {
                                await Task.Delay(3000);
                            }
                        }),
                            Task.Run(async() =>
                        {
                            await Task.Delay(100);
                            hostSecrets = await secretManager.GetHostSecretsAsync();
                        }));
                    });
                }
            }
        }
        public async Task <string> GetMasterKeyAsync()
        {
            HostSecretsInfo secrets = await SecretManager.GetHostSecretsAsync();

            return(secrets.MasterKey);
        }