コード例 #1
0
        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);
            }
        }
コード例 #2
0
        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);
            }
        }
コード例 #3
0
        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());
            }
        }
コード例 #4
0
        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);
                        }));
                    });
                }
            }
        }
コード例 #5
0
        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();
                        }));
                    });
                }
            }
        }
コード例 #6
0
        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.");
                }
            }
コード例 #8
0
        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();
                }
            }
        }
コード例 #9
0
        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");
            }
        }