private static void LogBoundAddresses(IFeatureCollection features, AddressListenResults results, ILogger logger)
        {
            IServerAddressesFeature serverAddresses = features.Get <IServerAddressesFeature>();

            // This logging allows the tool to differentiate which addresses
            // are default address and which are metrics addresses.

            foreach (string defaultAddress in results.GetDefaultAddresses(serverAddresses))
            {
                logger.BoundDefaultAddress(defaultAddress);
            }

            foreach (string metricAddress in results.GetMetricsAddresses(serverAddresses))
            {
                logger.BoundMetricsAddress(metricAddress);
            }
        }
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(
            IApplicationBuilder app,
            IHostApplicationLifetime lifetime,
            IWebHostEnvironment env,
            ExperimentalToolLogger exprLogger,
            IAuthOptions options,
            AddressListenResults listenResults,
            ILogger <Startup> logger)
        {
            exprLogger.LogExperimentMessage();

            // These errors are populated before Startup.Configure is called because
            // the KestrelServer class is configured as a prerequisite of
            // GenericWebHostServer being instantiated. The GenericWebHostServer invokes
            // Startup.Configure as part of its StartAsync method. This method is the
            // first opportunity to log anything through ILogger (a dedicated HostedService
            // could be written for this, but there is no guarantee that service would run
            // after the GenericWebHostServer is instantiated but before it is started).
            foreach (AddressListenResult result in listenResults.Errors)
            {
                logger.UnableToListenToAddress(result.Url, result.Exception);
            }

            // If we end up not listening on any ports, Kestrel defaults to port 5000. Make sure we don't attempt this.
            // Startup.Configure is called before KestrelServer is started
            // by the GenericWebHostServer, so there is no duplication of logging errors
            // and Kestrel does not bind to default ports.
            if (!listenResults.AnyAddresses)
            {
                // This is logged by GenericWebHostServer.StartAsync
                throw new MonitoringException("Unable to bind any urls.");
            }

            lifetime.ApplicationStarted.Register(() => LogBoundAddresses(app.ServerFeatures, listenResults, logger));

            if (options.KeyAuthenticationMode == KeyAuthenticationMode.NoAuth)
            {
                logger.NoAuthentication();
            }
            else
            {
                //Auth is enabled and we are binding on http. Make sure we log a warning.

                string   hostingUrl = Configuration.GetValue <string>(WebHostDefaults.ServerUrlsKey);
                string[] urls       = ConfigurationHelper.SplitValue(hostingUrl);
                foreach (string url in urls)
                {
                    BindingAddress address = null;
                    try
                    {
                        address = BindingAddress.Parse(url);
                    }
                    catch (Exception)
                    {
                        continue;
                    }

                    if (string.Equals(Uri.UriSchemeHttp, address.Scheme, StringComparison.OrdinalIgnoreCase))
                    {
                        logger.InsecureAuthenticationConfiguration();
                        break;
                    }
                }
            }

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseHsts();
            }

            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            CorsConfiguration corsConfiguration = new CorsConfiguration();

            Configuration.Bind(nameof(CorsConfiguration), corsConfiguration);
            if (!string.IsNullOrEmpty(corsConfiguration.AllowedOrigins))
            {
                app.UseCors(builder => builder.WithOrigins(corsConfiguration.GetOrigins()).AllowAnyHeader().AllowAnyMethod());
            }

            app.UseResponseCompression();

            //Note this must be after UseRouting but before UseEndpoints
            app.UseMiddleware <Throttling>();

            app.UseEndpoints(builder =>
            {
                builder.MapControllers();
            });
        }
 public MetricsPortsProvider(AddressListenResults results, IServer server)
 {
     _results         = results;
     _serverAddresses = server.Features.Get <IServerAddressesFeature>();
 }
        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 #5
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>();
            }));
        }