1. Specified RequireHttpsPermanent and SSL port to MVC pipeline (Line 74-77)

2. Add SetCompatibilityVersion to 2.1 in the MVC pipeline (Line 82)
3. Configured HttpsRedirection in the DI container (Line 83-85) and used HttpsRedirection MiddleWare (Line 106)
4. Use HSTS (Http Secured Transport Security) MiddleWare on Staging or Production (Line 101-103)
This commit is contained in:
rafsanulhasan 2018-09-01 03:50:55 +06:00
parent 6226b4a6fa
commit 065b0112e6
2 changed files with 269 additions and 244 deletions

View File

@ -22,163 +22,177 @@ using System.Reflection;
namespace Microsoft.eShopOnContainers.Services.Identity.API namespace Microsoft.eShopOnContainers.Services.Identity.API
{ {
public class Startup public class Startup
{ {
public Startup(IConfiguration configuration) public Startup(IConfiguration configuration)
{ {
Configuration = configuration; Configuration = configuration;
} }
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services) public IServiceProvider ConfigureServices(IServiceCollection services)
{ {
RegisterAppInsights(services); RegisterAppInsights(services);
// Add framework services. // Add framework services.
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration["ConnectionString"], options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions => sqlServerOptionsAction: sqlOptions =>
{ {
sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
})); }));
services.AddIdentity<ApplicationUser, IdentityRole>() services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>() .AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders(); .AddDefaultTokenProviders();
services.Configure<AppSettings>(Configuration); services.Configure<AppSettings>(Configuration);
services.AddMvc(); services
.AddMvc(opts=>
{
opts.SslPort = 4105;
opts.RequireHttpsPermanent = true;
})
.SetCompatibilityVersion(AspNetCore.Mvc.CompatibilityVersion.Version_2_1)
;
if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString) if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
{ {
services.AddDataProtection(opts => services.AddDataProtection(opts =>
{ {
opts.ApplicationDiscriminator = "eshop.identity"; opts.ApplicationDiscriminator = "eshop.identity";
}) })
.PersistKeysToRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys"); .PersistKeysToRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys");
} }
services.AddHealthChecks(checks => services.AddHealthChecks(checks =>
{ {
var minutes = 1; int minutes = 1;
if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) if (int.TryParse(Configuration["HealthCheck:Timeout"], out int minutesParsed))
{ {
minutes = minutesParsed; minutes = minutesParsed;
} }
checks.AddSqlCheck("Identity_Db", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes)); checks.AddSqlCheck("Identity_Db", Configuration["ConnectionString"], TimeSpan.FromMinutes(minutes));
}); });
services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>(); services.AddTransient<ILoginService<ApplicationUser>, EFLoginService>();
services.AddTransient<IRedirectService, RedirectService>(); services.AddTransient<IRedirectService, RedirectService>();
var connectionString = Configuration["ConnectionString"]; string connectionString = Configuration["ConnectionString"];
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; string migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// Adds IdentityServer // Adds IdentityServer
services.AddIdentityServer(x => x.IssuerUri = "null") services.AddIdentityServer(x => x.IssuerUri = "null")
.AddSigningCredential(Certificate.Get()) .AddSigningCredential(Certificate.Get())
.AddAspNetIdentity<ApplicationUser>() .AddAspNetIdentity<ApplicationUser>()
.AddConfigurationStore(options => .AddConfigurationStore(options =>
{ {
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions => sqlServerOptionsAction: sqlOptions =>
{ {
sqlOptions.MigrationsAssembly(migrationsAssembly); sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
}); });
}) })
.AddOperationalStore(options => .AddOperationalStore(options =>
{ {
options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString,
sqlServerOptionsAction: sqlOptions => sqlServerOptionsAction: sqlOptions =>
{ {
sqlOptions.MigrationsAssembly(migrationsAssembly); sqlOptions.MigrationsAssembly(migrationsAssembly);
//Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency //Configuring Connection Resiliency: https://docs.microsoft.com/en-us/ef/core/miscellaneous/connection-resiliency
sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
}); });
}) })
.Services.AddTransient<IProfileService, ProfileService>(); .Services.AddTransient<IProfileService, ProfileService>();
var container = new ContainerBuilder(); services.AddHttpsRedirection(opts =>
container.Populate(services); {
opts.HttpsPort = 4105;
});
return new AutofacServiceProvider(container.Build()); ContainerBuilder container = new ContainerBuilder();
} container.Populate(services);
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. return new AutofacServiceProvider(container.Build());
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) }
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddAzureWebAppDiagnostics();
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
if (env.IsDevelopment()) // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
{ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
app.UseDeveloperExceptionPage(); {
app.UseDatabaseErrorPage(); loggerFactory.AddConsole(Configuration.GetSection("Logging"));
} loggerFactory.AddDebug();
else loggerFactory.AddAzureWebAppDiagnostics();
{ loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
app.UseExceptionHandler("/Home/Error");
}
var pathBase = Configuration["PATH_BASE"]; if (env.IsDevelopment())
if (!string.IsNullOrEmpty(pathBase)) {
{ app.UseDeveloperExceptionPage();
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); app.UseDatabaseErrorPage();
app.UsePathBase(pathBase); }
} else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
string pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
app.UsePathBase(pathBase);
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200));
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
app.UseStaticFiles(); app.UseStaticFiles();
// Make work identity server redirections in Edge and lastest versions of browers. WARN: Not valid in a production environment. // Make work identity server redirections in Edge and lastest versions of browers. WARN: Not valid in a production environment.
app.Use(async (context, next) => app.Use(async (context, next) =>
{ {
context.Response.Headers.Add("Content-Security-Policy", "script-src 'unsafe-inline'"); context.Response.Headers.Add("Content-Security-Policy", "script-src 'unsafe-inline'");
await next(); await next();
}); });
app.UseForwardedHeaders(); app.UseForwardedHeaders();
// Adds IdentityServer // Adds IdentityServer
app.UseIdentityServer(); app.UseIdentityServer();
app.UseMvc(routes => app.UseMvc(routes =>
{ {
routes.MapRoute( routes.MapRoute(
name: "default", name: "default",
template: "{controller=Home}/{action=Index}/{id?}"); template: "{controller=Home}/{action=Index}/{id?}");
}); });
} }
private void RegisterAppInsights(IServiceCollection services) private void RegisterAppInsights(IServiceCollection services)
{ {
services.AddApplicationInsightsTelemetry(Configuration); services.AddApplicationInsightsTelemetry(Configuration);
var orchestratorType = Configuration.GetValue<string>("OrchestratorType"); string orchestratorType = Configuration.GetValue<string>("OrchestratorType");
if (orchestratorType?.ToUpper() == "K8S") if (orchestratorType?.ToUpper() == "K8S")
{ {
// Enable K8s telemetry initializer // Enable K8s telemetry initializer
services.EnableKubernetes(); services.EnableKubernetes();
} }
if (orchestratorType?.ToUpper() == "SF") if (orchestratorType?.ToUpper() == "SF")
{ {
// Enable SF telemetry initializer // Enable SF telemetry initializer
services.AddSingleton<ITelemetryInitializer>((serviceProvider) => services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
new FabricTelemetryInitializer()); new FabricTelemetryInitializer());
} }
} }
} }
} }

View File

@ -17,142 +17,153 @@ using WebSPA.Infrastructure;
namespace eShopConContainers.WebSPA namespace eShopConContainers.WebSPA
{ {
public class Startup public class Startup
{ {
public Startup(IConfiguration configuration) public Startup(IConfiguration configuration)
{ {
Configuration = configuration; Configuration = configuration;
} }
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
private IHostingEnvironment _hostingEnv; private readonly IHostingEnvironment _hostingEnv;
public Startup(IHostingEnvironment env) public Startup(IHostingEnvironment env)
{ {
_hostingEnv = env; _hostingEnv = env;
var localPath = new Uri(Configuration["ASPNETCORE_URLS"])?.LocalPath ?? "/"; string localPath = new Uri(Configuration["ASPNETCORE_URLS"])?.LocalPath ?? "/";
Configuration["BaseUrl"] = localPath; Configuration["BaseUrl"] = localPath;
} }
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
RegisterAppInsights(services); RegisterAppInsights(services);
services.AddHealthChecks(checks => services.AddHealthChecks(checks =>
{ {
var minutes = 1; int minutes = 1;
if (int.TryParse(Configuration["HealthCheck:Timeout"], out var minutesParsed)) if (int.TryParse(Configuration["HealthCheck:Timeout"], out int minutesParsed))
{ {
minutes = minutesParsed; minutes = minutesParsed;
} }
checks.AddUrlCheck(Configuration["CatalogUrlHC"], TimeSpan.FromMinutes(minutes)); checks.AddUrlCheck(Configuration["CatalogUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(Configuration["OrderingUrlHC"], TimeSpan.FromMinutes(minutes)); checks.AddUrlCheck(Configuration["OrderingUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(Configuration["BasketUrlHC"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos checks.AddUrlCheck(Configuration["BasketUrlHC"], TimeSpan.Zero); //No cache for this HealthCheck, better just for demos
checks.AddUrlCheck(Configuration["IdentityUrlHC"], TimeSpan.FromMinutes(minutes)); checks.AddUrlCheck(Configuration["IdentityUrlHC"], TimeSpan.FromMinutes(minutes));
checks.AddUrlCheck(Configuration["MarketingUrlHC"], TimeSpan.FromMinutes(minutes)); checks.AddUrlCheck(Configuration["MarketingUrlHC"], TimeSpan.FromMinutes(minutes));
});
services.Configure<AppSettings>(Configuration); });
if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString) services.Configure<AppSettings>(Configuration);
{
services.AddDataProtection(opts =>
{
opts.ApplicationDiscriminator = "eshop.webspa";
})
.PersistKeysToRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys");
}
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN"); if (Configuration.GetValue<string>("IsClusterEnv") == bool.TrueString)
{
services.AddDataProtection(opts =>
{
opts.ApplicationDiscriminator = "eshop.webspa";
})
.PersistKeysToRedis(ConnectionMultiplexer.Connect(Configuration["DPConnectionString"]), "DataProtection-Keys");
}
services.AddMvc() services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
.AddJsonOptions(options =>
{ services.AddMvc()
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); .AddJsonOptions(options =>
}); {
} options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
services.AddHttpsRedirection(opts =>
{
opts.HttpsPort = 4104;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IAntiforgery antiforgery) public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IAntiforgery antiforgery)
{ {
loggerFactory.AddAzureWebAppDiagnostics(); loggerFactory.AddAzureWebAppDiagnostics();
loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace); loggerFactory.AddApplicationInsights(app.ApplicationServices, LogLevel.Trace);
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
} }
else
{
app.UseHsts();
}
// Configure XSRF middleware, This pattern is for SPA style applications where XSRF token is added on Index page app.UseHttpsRedirection();
// load and passed back token on every subsequent async request
// app.Use(async (context, next) =>
// {
// if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase))
// {
// var tokens = antiforgery.GetAndStoreTokens(context);
// context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
// }
// await next.Invoke();
// });
//Seed Data // Configure XSRF middleware, This pattern is for SPA style applications where XSRF token is added on Index page
WebContextSeed.Seed(app, env, loggerFactory); // load and passed back token on every subsequent async request
// app.Use(async (context, next) =>
// {
// if (string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase))
// {
// var tokens = antiforgery.GetAndStoreTokens(context);
// context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
// }
// await next.Invoke();
// });
var pathBase = Configuration["PATH_BASE"]; //Seed Data
if (!string.IsNullOrEmpty(pathBase)) WebContextSeed.Seed(app, env, loggerFactory);
{
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'"); string pathBase = Configuration["PATH_BASE"];
app.UsePathBase(pathBase); if (!string.IsNullOrEmpty(pathBase))
} {
loggerFactory.CreateLogger("init").LogDebug($"Using PATH BASE '{pathBase}'");
app.UsePathBase(pathBase);
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200)); app.Map("/liveness", lapp => lapp.Run(async ctx => ctx.Response.StatusCode = 200));
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
app.Use(async (context, next) => app.Use(async (context, next) =>
{ {
await next(); await next();
// If there's no available file and the request doesn't contain an extension, we're probably trying to access a page. // If there's no available file and the request doesn't contain an extension, we're probably trying to access a page.
// Rewrite request to use app root // Rewrite request to use app root
if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value) && !context.Request.Path.Value.StartsWith("/api")) if (context.Response.StatusCode == 404 && !Path.HasExtension(context.Request.Path.Value) && !context.Request.Path.Value.StartsWith("/api"))
{ {
context.Request.Path = "/index.html"; context.Request.Path = "/index.html";
context.Response.StatusCode = 200; // Make sure we update the status code, otherwise it returns 404 context.Response.StatusCode = 200; // Make sure we update the status code, otherwise it returns 404
await next(); await next();
} }
}); });
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute(); app.UseDefaultFiles();
} app.UseStaticFiles();
private void RegisterAppInsights(IServiceCollection services) app.UseMvcWithDefaultRoute();
{ }
services.AddApplicationInsightsTelemetry(Configuration);
var orchestratorType = Configuration.GetValue<string>("OrchestratorType");
if (orchestratorType?.ToUpper() == "K8S") private void RegisterAppInsights(IServiceCollection services)
{ {
// Enable K8s telemetry initializer services.AddApplicationInsightsTelemetry(Configuration);
services.EnableKubernetes(); string orchestratorType = Configuration.GetValue<string>("OrchestratorType");
}
if (orchestratorType?.ToUpper() == "SF") if (orchestratorType?.ToUpper() == "K8S")
{ {
// Enable SF telemetry initializer // Enable K8s telemetry initializer
services.AddSingleton<ITelemetryInitializer>((serviceProvider) => services.EnableKubernetes();
new FabricTelemetryInitializer()); }
} if (orchestratorType?.ToUpper() == "SF")
} {
} // Enable SF telemetry initializer
services.AddSingleton<ITelemetryInitializer>((serviceProvider) =>
new FabricTelemetryInitializer());
}
}
}
} }