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>()); }
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); }
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); }