public static IHostBuilder CreateHostBuilder(IConsole console, string[] urls, string[] metricUrls, bool metrics, string diagnosticPort, bool noAuth)
        {
            return(Host.CreateDefaultBuilder()
                   .UseContentRoot(AppContext.BaseDirectory) // Use the application root instead of the current directory
                   .ConfigureAppConfiguration((IConfigurationBuilder builder) =>
            {
                //Note these are in precedence order.
                ConfigureEndpointInfoSource(builder, diagnosticPort);
                ConfigureMetricsEndpoint(builder, metrics, metricUrls);
                builder.AddCommandLine(new[] { "--urls", ConfigurationHelper.JoinValue(urls) });

                builder.AddJsonFile(UserSettingsPath, optional: true, reloadOnChange: true);
                builder.AddJsonFile(SharedSettingsPath, optional: true, reloadOnChange: true);

                //HACK Workaround for https://github.com/dotnet/runtime/issues/36091
                //KeyPerFile provider uses a file system watcher to trigger changes.
                //The watcher does not follow symlinks inside the watched directory, such as mounted files
                //in Kubernetes.
                //We get around this by watching the target folder of the symlink instead.
                //See https://github.com/kubernetes/kubernetes/master/pkg/volume/util/atomic_writer.go
                string path = SharedConfigDirectoryPath;
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && RuntimeInfo.IsInKubernetes)
                {
                    string symlinkTarget = Path.Combine(SharedConfigDirectoryPath, "..data");
                    if (Directory.Exists(symlinkTarget))
                    {
                        path = symlinkTarget;
                    }
                }

                builder.AddKeyPerFile(path, optional: true, reloadOnChange: true);
                builder.AddEnvironmentVariables(ConfigPrefix);
            })
                   .ConfigureServices((HostBuilderContext context, IServiceCollection services) =>
            {
                //TODO Many of these service additions should be done through extension methods

                AuthOptions authenticationOptions = new AuthOptions(noAuth ? KeyAuthenticationMode.NoAuth : KeyAuthenticationMode.StoredKey);
                services.AddSingleton <IAuthOptions>(authenticationOptions);

                List <string> authSchemas = null;
                if (authenticationOptions.EnableKeyAuth)
                {
                    //Add support for Authentication and Authorization.
                    AuthenticationBuilder authBuilder = services.AddAuthentication(options =>
                    {
                        options.DefaultAuthenticateScheme = AuthConstants.ApiKeySchema;
                        options.DefaultChallengeScheme = AuthConstants.ApiKeySchema;
                    })
                                                        .AddScheme <ApiKeyAuthenticationHandlerOptions, ApiKeyAuthenticationHandler>(AuthConstants.ApiKeySchema, _ => { });

                    authSchemas = new List <string> {
                        AuthConstants.ApiKeySchema
                    };

                    if (authenticationOptions.EnableNegotiate)
                    {
                        //On Windows add Negotiate package. This will use NTLM to perform Windows Authentication.
                        authBuilder.AddNegotiate();
                        authSchemas.Add(AuthConstants.NegotiateSchema);
                    }
                }

                //Apply Authorization Policy for NTLM. Without Authorization, any user with a valid login/password will be authorized. We only
                //want to authorize the same user that is running dotnet-monitor, at least for now.
                //Note this policy applies to both Authorization schemas.
                services.AddAuthorization(authOptions =>
                {
                    if (authenticationOptions.EnableKeyAuth)
                    {
                        authOptions.AddPolicy(AuthConstants.PolicyName, (builder) =>
                        {
                            builder.AddRequirements(new AuthorizedUserRequirement());
                            builder.RequireAuthenticatedUser();
                            builder.AddAuthenticationSchemes(authSchemas.ToArray());
                        });
                    }
                    else
                    {
                        authOptions.AddPolicy(AuthConstants.PolicyName, (builder) =>
                        {
                            builder.RequireAssertion((_) => true);
                        });
                    }
                });

                if (authenticationOptions.EnableKeyAuth)
                {
                    services.AddSingleton <IAuthorizationHandler, UserAuthorizationHandler>();
                }

                services.Configure <DiagnosticPortOptions>(context.Configuration.GetSection(ConfigurationKeys.DiagnosticPort));
                services.AddSingleton <IEndpointInfoSource, FilteredEndpointInfoSource>();
                services.AddHostedService <FilteredEndpointInfoSourceHostedService>();
                services.AddSingleton <IDiagnosticServices, DiagnosticServices>();
                services.ConfigureEgress(context.Configuration);
                services.ConfigureMetrics(context.Configuration);
                services.AddSingleton <ExperimentalToolLogger>();
            })
                   .ConfigureLogging(builder =>
            {
                // Always allow the experimental tool message to be logged
                ExperimentalToolLogger.AddLogFilter(builder);
            })
                   .ConfigureWebHostDefaults(webBuilder =>
            {
                AddressListenResults listenResults = new AddressListenResults();
                webBuilder.ConfigureServices(services =>
                {
                    services.AddSingleton(listenResults);
                })
                .ConfigureKestrel((context, options) =>
                {
                    //Note our priorities for hosting urls don't match the default behavior.
                    //Default Kestrel behavior priority
                    //1) ConfigureKestrel settings
                    //2) Command line arguments (--urls)
                    //3) Environment variables (ASPNETCORE_URLS, then DOTNETCORE_URLS)

                    //Our precedence
                    //1) Environment variables (ASPNETCORE_URLS, DotnetMonitor_Metrics__Endpoints)
                    //2) Command line arguments (these have defaults) --urls, --metricUrls
                    //3) ConfigureKestrel is used for fine control of the server, but honors the first two configurations.

                    string hostingUrl = context.Configuration.GetValue <string>(WebHostDefaults.ServerUrlsKey);
                    urls = ConfigurationHelper.SplitValue(hostingUrl);

                    var metricsOptions = new MetricsOptions();
                    context.Configuration.Bind(ConfigurationKeys.Metrics, metricsOptions);

                    //Workaround for lack of default certificate. See https://github.com/dotnet/aspnetcore/issues/28120
                    options.Configure(context.Configuration.GetSection("Kestrel")).Load();

                    //By default, we bind to https for sensitive data (such as dumps and traces) and bind http for
                    //non-sensitive data such as metrics. We may be missing a certificate for https binding. We want to continue with the
                    //http binding in that scenario.
                    metricUrls = metricsOptions.Enabled.GetValueOrDefault(MetricsOptionsDefaults.Enabled) ?
                                 ProcessMetricUrls(metricUrls, metricsOptions) :
                                 Array.Empty <string>();

                    listenResults.Listen(options, urls, metricUrls);
                })
                .UseStartup <Startup>();
            }));
        }
Exemple #2
0
        public static IHostBuilder CreateHostBuilder(string[] urls, string[] metricUrls, bool metrics, string diagnosticPort, AuthConfiguration authenticationOptions)
        {
            return(Host.CreateDefaultBuilder()
                   .UseContentRoot(AppContext.BaseDirectory) // Use the application root instead of the current directory
                   .ConfigureHostConfiguration((IConfigurationBuilder builder) =>
            {
                //Note these are in precedence order.
                ConfigureEndpointInfoSource(builder, diagnosticPort);
                ConfigureMetricsEndpoint(builder, metrics, metricUrls);
                ConfigureGlobalMetrics(builder);
                builder.ConfigureStorageDefaults();

                builder.AddCommandLine(new[] { "--urls", ConfigurationHelper.JoinValue(urls) });
            })
                   .ConfigureAppConfiguration((IConfigurationBuilder builder) =>
            {
                builder.AddJsonFile(UserSettingsPath, optional: true, reloadOnChange: true);
                builder.AddJsonFile(SharedSettingsPath, optional: true, reloadOnChange: true);

                //HACK Workaround for https://github.com/dotnet/runtime/issues/36091
                //KeyPerFile provider uses a file system watcher to trigger changes.
                //The watcher does not follow symlinks inside the watched directory, such as mounted files
                //in Kubernetes.
                //We get around this by watching the target folder of the symlink instead.
                //See https://github.com/kubernetes/kubernetes/master/pkg/volume/util/atomic_writer.go
                string path = SharedConfigDirectoryPath;
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && RuntimeInfo.IsInKubernetes)
                {
                    string symlinkTarget = Path.Combine(SharedConfigDirectoryPath, "..data");
                    if (Directory.Exists(symlinkTarget))
                    {
                        path = symlinkTarget;
                    }
                }

                builder.AddKeyPerFile(path, optional: true, reloadOnChange: true);
                builder.AddEnvironmentVariables(ConfigPrefix);

                if (authenticationOptions.KeyAuthenticationMode == KeyAuthenticationMode.TemporaryKey)
                {
                    ConfigureTempApiHashKey(builder, authenticationOptions);
                }
            })
                   //Note this is necessary for config only because Kestrel configuration
                   //is not added until WebHostDefaults are added.
                   .ConfigureWebHostDefaults(webBuilder =>
            {
                AddressListenResults listenResults = new AddressListenResults();
                webBuilder.ConfigureServices(services =>
                {
                    services.AddSingleton(listenResults);
                })
                .ConfigureKestrel((context, options) =>
                {
                    //Note our priorities for hosting urls don't match the default behavior.
                    //Default Kestrel behavior priority
                    //1) ConfigureKestrel settings
                    //2) Command line arguments (--urls)
                    //3) Environment variables (ASPNETCORE_URLS, then DOTNETCORE_URLS)

                    //Our precedence
                    //1) Environment variables (ASPNETCORE_URLS, DotnetMonitor_Metrics__Endpoints)
                    //2) Command line arguments (these have defaults) --urls, --metricUrls
                    //3) ConfigureKestrel is used for fine control of the server, but honors the first two configurations.

                    string hostingUrl = context.Configuration.GetValue <string>(WebHostDefaults.ServerUrlsKey);
                    urls = ConfigurationHelper.SplitValue(hostingUrl);

                    var metricsOptions = new MetricsOptions();
                    context.Configuration.Bind(ConfigurationKeys.Metrics, metricsOptions);

                    string metricHostingUrls = metricsOptions.Endpoints;
                    metricUrls = ConfigurationHelper.SplitValue(metricHostingUrls);

                    //Workaround for lack of default certificate. See https://github.com/dotnet/aspnetcore/issues/28120
                    options.Configure(context.Configuration.GetSection("Kestrel")).Load();

                    //By default, we bind to https for sensitive data (such as dumps and traces) and bind http for
                    //non-sensitive data such as metrics. We may be missing a certificate for https binding. We want to continue with the
                    //http binding in that scenario.
                    listenResults.Listen(
                        options,
                        urls,
                        metricsOptions.Enabled.GetValueOrDefault(MetricsOptionsDefaults.Enabled) ? metricUrls : Array.Empty <string>());
                })
                .UseStartup <Startup>();
            }));
        }