public void Constructor_WithCreateHostSecretsIfMissingSet_CreatesHostSecret() { var secretsPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); var hostSecretPath = Path.Combine(secretsPath, ScriptConstants.HostMetadataFileName); try { string expectedTraceMessage = Resources.TraceHostSecretGeneration; bool preExistingFile = File.Exists(hostSecretPath); Mock <IKeyValueConverterFactory> mockValueConverterFactory = GetConverterFactoryMock(false, false); ISecretsRepository repository = new FileSystemSecretsRepository(secretsPath); var secretManager = new SecretManager(repository, mockValueConverterFactory.Object, _logger, new TestMetricsLogger(), _hostNameProvider, true); bool fileCreated = File.Exists(hostSecretPath); Assert.False(preExistingFile); Assert.True(fileCreated); } finally { Directory.Delete(secretsPath, true); } }
public async Task SetMasterKey_WithoutProvidedKey_GeneratesKeyAndPersistsFile() { using (var directory = new TempDirectory()) { 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)) { result = await secretManager.SetMasterKeyAsync(); } bool functionSecretsExists = File.Exists(Path.Combine(directory.Path, "testfunction.json")); string secretsJson = File.ReadAllText(Path.Combine(directory.Path, ScriptConstants.HostMetadataFileName)); HostSecrets persistedSecrets = ScriptSecretSerializer.DeserializeSecrets <HostSecrets>(secretsJson); Assert.NotNull(persistedSecrets); Assert.NotNull(persistedSecrets.MasterKey); Assert.Equal(OperationResult.Created, result.Result); Assert.Equal(result.Secret, persistedSecrets.MasterKey.Value); } }
public async Task GetFunctionSecrets_WhenNoSecretFileExists_CreatesDefaultSecretAndPersistsFile() { using (var directory = new TempDirectory()) { string functionName = "TestFunction"; string expectedTraceMessage = string.Format(Resources.TraceFunctionSecretGeneration, functionName); Mock <IKeyValueConverterFactory> mockValueConverterFactory = GetConverterFactoryMock(false, false); IDictionary <string, string> functionSecrets; ISecretsRepository repository = new FileSystemSecretsRepository(directory.Path); using (var secretManager = new SecretManager(repository, mockValueConverterFactory.Object, _logger, new TestMetricsLogger(), _hostNameProvider)) { functionSecrets = await secretManager.GetFunctionSecretsAsync(functionName); } bool functionSecretsExists = File.Exists(Path.Combine(directory.Path, "testfunction.json")); Assert.NotNull(functionSecrets); Assert.True(functionSecretsExists); Assert.Equal(1, functionSecrets.Count); Assert.Equal(ScriptConstants.DefaultFunctionKeyName, functionSecrets.Keys.First()); } }
public async Task GetFunctionSecretsAsync_WaitsForNewSecrets() { using (var directory = new TempDirectory()) { string functionName = "testfunction"; string functionSecretsJson = @"{ 'keys': [ { 'name': 'Key1', 'value': 'FunctionValue1', 'encrypted': false }, { 'name': 'Key2', 'value': 'FunctionValue2', 'encrypted': false } ] }"; string filePath = Path.Combine(directory.Path, functionName + ".json"); File.WriteAllText(filePath, functionSecretsJson); IDictionary <string, string> functionSecrets = 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); functionSecrets = await secretManager.GetFunctionSecretsAsync(functionName); })); Assert.Equal(functionSecrets["Key1"], "FunctionValue1"); } 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); functionSecrets = await secretManager.GetFunctionSecretsAsync(functionName); })); }); } } }
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 GetHostSecrets_WhenTooManyBackups_ThrowsException() { using (var directory = new TempDirectory()) { string functionName = "testfunction"; string expectedTraceMessage = string.Format(Resources.ErrorTooManySecretBackups, ScriptConstants.MaximumSecretBackupCount, functionName, string.Format(Resources.ErrorSameSecrets, "test0,test1")); string functionSecretsJson = @"{ 'keys': [ { 'name': 'Key1', 'value': 'FunctionValue1', 'encrypted': true }, { 'name': 'Key2', 'value': 'FunctionValue2', 'encrypted': false } ] }"; ILoggerFactory loggerFactory = new LoggerFactory(); TestLoggerProvider loggerProvider = new TestLoggerProvider(); loggerFactory.AddProvider(loggerProvider); var logger = loggerFactory.CreateLogger(LogCategories.CreateFunctionCategory("Test1")); IDictionary <string, string> functionSecrets; ISecretsRepository repository = new FileSystemSecretsRepository(directory.Path); using (var secretManager = new SecretManager(repository, logger, new TestMetricsLogger(), _hostNameProvider)) { InvalidOperationException ioe = null; try { for (int i = 0; i < ScriptConstants.MaximumSecretBackupCount + 20; i++) { File.WriteAllText(Path.Combine(directory.Path, functionName + ".json"), functionSecretsJson); // If we haven't hit the exception yet, pause to ensure the file contents are being flushed. if (i >= ScriptConstants.MaximumSecretBackupCount) { await Task.Delay(500); } // reset hostname provider and set a new hostname to force another backup _hostNameProvider.Reset(); string hostName = "test" + (i % 2).ToString(); _testEnvironment.SetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteHostName, hostName); functionSecrets = await secretManager.GetFunctionSecretsAsync(functionName); } } catch (InvalidOperationException ex) { ioe = ex; } } Assert.True(Directory.GetFiles(directory.Path, $"{functionName}.{ScriptConstants.Snapshot}*").Length >= ScriptConstants.MaximumSecretBackupCount); Assert.True(loggerProvider.GetAllLogMessages().Any( t => t.Level == LogLevel.Debug && t.FormattedMessage.IndexOf(expectedTraceMessage, StringComparison.OrdinalIgnoreCase) > -1), "Expected Trace message not found"); } }
public Fixture() { EventGenerator = new TestSystemEventGenerator(); _settingsManager = ScriptSettingsManager.Instance; TestFunctionRoot = Path.Combine(TestHelpers.FunctionsTestDirectory, "Functions"); TestLogsRoot = Path.Combine(TestHelpers.FunctionsTestDirectory, "Logs"); TestSecretsRoot = Path.Combine(TestHelpers.FunctionsTestDirectory, "Secrets"); string testRoot = Path.Combine(TestFunctionRoot, Guid.NewGuid().ToString()); SecretsPath = Path.Combine(TestSecretsRoot, Guid.NewGuid().ToString()); Directory.CreateDirectory(SecretsPath); string logRoot = Path.Combine(TestLogsRoot, Guid.NewGuid().ToString(), @"Functions"); Directory.CreateDirectory(logRoot); FunctionsLogDir = Path.Combine(logRoot, @"Function"); Directory.CreateDirectory(FunctionsLogDir); // Add some secret files (both old and valid) File.WriteAllText(Path.Combine(SecretsPath, ScriptConstants.HostMetadataFileName), string.Empty); File.WriteAllText(Path.Combine(SecretsPath, "WebHookTrigger.json"), string.Empty); File.WriteAllText(Path.Combine(SecretsPath, "QueueTriggerToBlob.json"), string.Empty); File.WriteAllText(Path.Combine(SecretsPath, "Foo.json"), string.Empty); File.WriteAllText(Path.Combine(SecretsPath, "Bar.json"), string.Empty); File.WriteAllText(Path.Combine(SecretsPath, "Invalid.json"), string.Empty); // Add some old file directories CreateTestFunctionLogs(FunctionsLogDir, "Foo"); CreateTestFunctionLogs(FunctionsLogDir, "Bar"); CreateTestFunctionLogs(FunctionsLogDir, "Baz"); CreateTestFunctionLogs(FunctionsLogDir, "Invalid"); ScriptHostConfiguration config = new ScriptHostConfiguration { RootScriptPath = @"TestScripts\Node", RootLogPath = logRoot, FileLoggingMode = FileLoggingMode.Always }; ISecretsRepository repository = new FileSystemSecretsRepository(SecretsPath); ISecretManager secretManager = new SecretManager(_settingsManager, repository, NullTraceWriter.Instance, null); WebHostSettings webHostSettings = new WebHostSettings(); webHostSettings.SecretsPath = SecretsPath; var hostConfig = config.HostConfig; var testEventGenerator = new TestSystemEventGenerator(); hostConfig.AddService <IEventGenerator>(EventGenerator); var mockEventManager = new Mock <IScriptEventManager>(); var mockHostManager = new WebScriptHostManager(config, new TestSecretManagerFactory(secretManager), mockEventManager.Object, _settingsManager, webHostSettings); HostManager = mockHostManager; Task task = Task.Run(() => { HostManager.RunAndBlock(); }); TestHelpers.Await(() => { return(HostManager.State == ScriptHostState.Running); }).GetAwaiter().GetResult(); // verify startup system trace logs string[] expectedPatterns = new string[] { "Info Reading host configuration file", "Info Host configuration file read", "Info Host lock lease acquired by instance ID '(.+)'", "Info Function 'Excluded' is marked as excluded", @"Info Generating ([0-9]+) job function\(s\)", @"Info Starting Host \(HostId=function-tests-node, Version=(.+), ProcessId=[0-9]+, Debug=False, Attempt=0\)", "Info WebJobs.Indexing Found the following functions:", "Info The next 5 occurrences of the schedule will be:", "Info WebJobs.Host Job host started", "Error The following 1 functions are in error:" }; foreach (string pattern in expectedPatterns) { Assert.True(EventGenerator.Events.Any(p => Regex.IsMatch(p, pattern)), $"Expected trace event {pattern} not found."); } }
internal async Task GeneratedMethods_WithOutParams_DoNotCauseDeadlocks(string fixture) { var traceWriter = new TestTraceWriter(TraceLevel.Verbose); ScriptHostConfiguration config = new ScriptHostConfiguration() { RootScriptPath = @"TestScripts\FunctionGeneration", TraceWriter = traceWriter }; string secretsPath = Path.Combine(Path.GetTempPath(), @"FunctionTests\Secrets"); ISecretsRepository repository = new FileSystemSecretsRepository(secretsPath); WebHostSettings webHostSettings = new WebHostSettings(); webHostSettings.SecretsPath = secretsPath; var secretManager = new SecretManager(SettingsManager, repository, NullTraceWriter.Instance); using (var manager = new WebScriptHostManager(config, new TestSecretManagerFactory(secretManager), SettingsManager, webHostSettings)) { Thread runLoopThread = new Thread(_ => { manager.RunAndBlock(CancellationToken.None); }); runLoopThread.IsBackground = true; runLoopThread.Start(); await TestHelpers.Await(() => { return(manager.State == ScriptHostState.Running); }); var request = new HttpRequestMessage(HttpMethod.Get, String.Format("http://localhost/api/httptrigger-{0}", fixture)); FunctionDescriptor function = manager.GetHttpFunctionOrNull(request); SynchronizationContext currentContext = SynchronizationContext.Current; var resetEvent = new ManualResetEventSlim(); try { var requestThread = new Thread(() => { var context = new SingleThreadSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(context); manager.HandleRequestAsync(function, request, CancellationToken.None) .ContinueWith(task => resetEvent.Set()); Thread.Sleep(500); context.Run(); }); requestThread.IsBackground = true; requestThread.Start(); bool threadSignaled = resetEvent.Wait(TimeSpan.FromSeconds(10)); requestThread.Abort(); Assert.True(threadSignaled, "Thread execution did not complete"); } finally { SynchronizationContext.SetSynchronizationContext(currentContext); manager.Stop(); } } }
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; var traceWriter = new TestTraceWriter(System.Diagnostics.TraceLevel.Verbose); ISecretsRepository repository = new FileSystemSecretsRepository(directory.Path); using (var secretManager = new SecretManager(repository, mockValueConverterFactory.Object, null)) { 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"); } }