European ASP.NET 4.5 Hosting BLOG

BLOG about ASP.NET 4, ASP.NET 4.5 Hosting and Its Technology - Dedicated to European Windows Hosting Customer

European ASP.NET Core Hosting :: Alert Dialog From Controller Without JavaScript In View

clock May 9, 2019 12:27 by author Peter
We can show an alert dialog in the browser from Controller without using any JavaScript in the View, which saves our time and makes the popping up of dynamic data way faster.
 
Displaying an Alert Dialog popup can be done from the controller and even from the server-side, but it is very useful when you want to display an alert using much less code.
 
In your Controller, copy the below code just before your return code.

public ActionResult SmartRegister(csUser model)  
 {               
     User us = new User();  
     rfSocietyEntities db = new rfSocietyEntities();  
     if (ModelState.IsValid)  
     {  
         int count = db.Users.Where(a => a.Email.Equals(model.Email)).Count();  
         if (count == 0)  
         {  
             us.Admin = model.Admin;  
             us.Email = model.Email;  
             us.FullName = model.FullName;  
             us.Password = model.Password;  
             us.PhoneNo = model.PhoneNo;  
             db.Users.Add(us);  
             db.SaveChanges();  
             return RedirectToAction("Dashboard""Dashboard");  
         }  
         else  
         {  
             TempData["msg"] = "<script>alert('Email id already registered.');</script>";  
             return View (model);  
         }  
     }  
     else  
     {  
         TempData["msg"] = "<script>alert('Please Check Data entered or try later.');</script>";  
         return View(model);  
     }  
}    
In your View file, add the below code.
  @Html.Raw(TempData["msg"])




European ASP.NET Core Hosting - HostForLIFE.eu :: ASP.NET Core Security Headers

clock April 30, 2019 11:13 by author Peter

With the help of headers, your website could send some useful information to the browser. Let’s see how it is possible to add more protection to your website.
To add a header for each request, we can use middleware.

XSS and CSP
Still in the OWASP top 10, there is XSS - Cross-Site Scripting attack. Sure, it helps a lot to encode symbols before displaying text on the website (using any one of the HtmlEncoder, JavaScriptEncoder, and UrlEncoder). And, it’s better never to use @Html.Raw(). But it is also possible to add a header that will inform the browser to stop XSS attack. This kind of header is useful mostly for old browsers.
app.Use(async (context, next) =>  
{  
context.Response.Headers.Add("X-Xss-Protection", "1");  
await next();  
}); 


For new browsers, it is better to use CSP. Here is how it is possible to add the CSP header.
app.Use(async (context, next) =>  
{  
context.Response.Headers.Add(  
  "Content-Security-Policy",  
  "default-src 'self'; " +  
  "img-src 'self' myblobacc.blob.core.windows.net; " +  
  "font-src 'self'; " +  
  "style-src 'self'; " +  
  "script-src 'self' 'nonce-KIBdfgEKjb34ueiw567bfkshbvfi4KhtIUE3IWF' "+  
  " 'nonce-rewgljnOIBU3iu2btli4tbllwwe'; " +  
  "frame-src 'self';"+  
  "connect-src 'self';");  
await next();  
});  


In this example, it is allowed to run scripts.js files only from the current website (that is a meaning of ‘self’). And it is allowed to run 2 specified with “nonce” attribute scripts that are inserted in page inside script tag. For example, if you are using some script like this one inside your page.
<script>  
function showMessage() {  
alert("Just for demo");  
}   
</script>  

Then, you will be not able to run this script without adding ‘unsafe-inline’ into your CSP definition.

But adding ‘unsafe-inline’ means leaving your website not-protected. So, better move the script into .js file or use a nonce. Just add to your script attribute nonce with some random value. For example,
<script nonce="KUY8VewuvyUYVEIvEFue4vwyiuf"> </script>  

Then, you can add to your CSP script-scr value ‘nonce-KUY8VewuvyUYVEIvEFue4vwyiuf’ and you will be able to run scripts from exactly this <script> section.

‘unsafe-inlne’ is also related to events that are added to your html as attributes. Like onclick, onchange, onkeydown, onfocus. For example, instead of the following onclick event, you should add id or class to your element and call event from <script> or .js file.
<p onclick="showMessage()">Show message</p>  

Like this,
<p id="message-text">Show message</p>  

<script nonce=”KUY8VewuvyUYVEIvEFue4vwyiuf”>  
$(document).ready(function() {  
$("#message-text") (function() {  
alert( "Just for demo" );  
});   
});  
</script>  


X-Frame-Options
By default, it is possible to display your website inside an iframe. But with one small header, it is possible to disallow this. Why? Because someone could display your website inside a frame and place a transparent layer over it. And, the users would be thinking that they are clicking on your website buttons/links but in a real case, they would be clicking on items placed in the transparent layer. And as cookies still could be in the user’s browser, some operation could be authenticated. This kind of attack is called Clickjacking. And, here is a header to protect your website from this attack.
context.Response.Headers.Add("X-Frame-Options", "DENY");  

Content sniffing
By the next link File Upload XSS you can find a more or less fresh sample of how it is possible to inject JavaScript into an svg file. And if a file like this would be located on the server that would have content sniffing security enabled, then JavaScript wouldn’t work because svg extension doesn’t correspond to JS content. Hope you believe me now that the next header is required.
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");  

Referrer-Policy
One of the headers that is automatically added by browsers is “Referer”. It contains a site from which the user has been transferred. Sometimes, that is convenient for analytics. But sometimes, the URL could contain some private information that is better not to be disclosed.

If you don’t want to allow browsers to display your website as last visited in “Referer” header, please use the Referrer-Policy: no-referrer

Here is an example of all headers in one middleware.
app.Use(async (context, next) =>  
{  
context.Response.Headers.Add("X-Xss-Protection", "1");  
context.Response.Headers.Add("X-Frame-Options", "DENY");  
context.Response.Headers.Add("Referrer-Policy", "no-referrer");  
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");  
              context.Response.Headers.Add(  
  "Content-Security-Policy",  
  "default-src 'self'; " +  
  "img-src 'self' myblobacc.blob.core.windows.net; " +  
  "font-src 'self'; " +  
  "style-src 'self'; " +  
  "script-src 'self' 'nonce-KIBdfgEKjb34ueiw567bfkshbvfi4KhtIUE3IWF' "+  
  " 'nonce-rewgljnOIBU3iu2btli4tbllwwe'; " +  
  "frame-src 'self';"+  
  "connect-src 'self';");  
await next();  
});  


Sure, you can read information about each one header and change value to something more appropriate for your needs.
Strict-Transport-Security

For activating Strict-Transport-Security - web security policy mechanism that helps to protect your website from protocol downgrade attacks and cookie hijacking, add the next one to your middleware pipeline (or just don’t remove it),
app.UseHsts();  

This middleware will add “Strict-Transport-Security” header

Removing Server Header
Sometimes, headers could provide some information that is better to hide. To disable the Server header from Kestrel, you need to set AddServerHeader to false. Use UseKestrel() if your ASP.NET Core version is  lower than 2.2 and ConfigureKestrel() if not.
WebHost.CreateDefaultBuilder(args)  
     .UseKestrel(c => c.AddServerHeader = false)  
     .UseStartup<Startup>()  
     .Build();



European ASP.NET Core Hosting :: Create A Typed HttpClient With

clock April 23, 2019 10:57 by author Peter

HttpClient is used for sending HTTP requests and receiving HTTP responses from a resource identified by a URI. But, HttpClient has some issues. To read more on the issues of HttpClient, you can check this link. In the .NET Core 2.1 release, Microsoft has introduced a new way of designing HttpClients to solve these issues, and it's called HttpClientFactory. HttpClientFactory is an opinionated factory, available since .NET Core 2.1, for creating HttpClient instances in our applications. This means that we can create HttpClients and can register them in the HttpClientFactory in our application and can leverage the dependency injection capabilities of the .NET core to inject these HttpClients in our code. HttpClientFactory allows us to no longer care about the lifecycle of the HttpClient by leaving it to the framework.
 
There are three ways to use HttpClientFactory to instantiate HttpClients.

  • Default client
  • Named client
  • Typed client

In order to use the factory, we need to register it in the DI container. So, we need to use an extension method AddHttpClient() on IServiceCollection interface in our Startup.cs class. This will allow us to inject the HttpClient in our class constructors.
 
In this article, we will see how to create a Typed HttpClient using the HttpClient factory in a .NET core MVC application and use it for making HTTP calls. I prefer Typed HttpClient over the other two because,

    As the name suggests, typed clients provide type safety.
    Typed clients help in encapsulating the API calls when we are making use of the HttpClient at one place, thus making our code DRY (Don't Repeat Yourself). The other two will scatter the implementation details of making HTTP calls throughout the codebase.

We will make a simple MVC application to learn the workings of typed HttpClient. This application will receive the name of a movie and call a REST API to fetch the details of that movie and shall display it to the user. I will be using Visual Studio Code for developing the application. The REST API used for fetching the movie details is the OMDB API. The OMDB API is a RESTful web service to obtain movie information. This is a free API with a 1000 requests per day limit for a user. We need an API key for accessing this API. To know more details about this API you can check their website.

Create a folder called MovieFinder and open it in VS Code. Create an MVC application by running the following command in the terminal.
    dotnet new mvc --name MovieFinder 

This shall create a basic .NET Core MVC application. Now, let’s create a View Model class to hold the data from the OMDB API. So, let’s add a class named MovieDetailModel.
    public class MovieDetailModel 
    { 
        public string Title { get; set; } 
        public string Year { get; set; } 
        public string Director { get; set; } 
        public string Actors { get; set; } 
        public string IMDBRating { get; set; } 
        public string PosterImage { get; set; } 
        public string Plot { get; set; } 
    } 


Now, we need to create an interface for our typed client. Let's name it IMovieDetailsClient.
    public interface IMovieDetailsClient 
    { 
        Task<MovieDetailModel> GetMovieDetailsAsync(string movieName); 
    } 


This interface contains a single method, GetMovieDetailsAsync, which accepts the movie name as the parameter and shall return the details of that movie. Now we need to create a class which implements this interface. This class shall contain the actual logic of calling the OMDB API to fetch the movie details.
    public class MovieDetailsClient : IMovieDetailsClient 
    { 
        private readonly HttpClient _httpClient; 
     
        public MovieDetailsClient(HttpClient httpClient) 
        { 
            httpClient.BaseAddress = new Uri("http://www.omdbapi.com/"); 
            _httpClient = httpClient; 
        } 
     
        public async Task<MovieDetailModel> GetMovieDetailsAsync(string movieName) 
        { 
            var queryString = $"?t={movieName}&apikey=<your-api-key>"; 
            var response = await _httpClient.GetStringAsync(queryString); 
     
            JObject json = JObject.Parse(response); 
     
            if (json.SelectToken("Response").Value<string>() == "True") 
            { 
     
                var movieDetails = new MovieDetailModel 
                { 
                    Title = json.SelectToken("Title").Value<string>(), 
                    Year = json.SelectToken("Year").Value<string>(), 
                    Director = json.SelectToken("Director").Value<string>(), 
                    Actors = json.SelectToken("Actors").Value<string>(), 
                    IMDBRating = json.SelectToken("imdbRating").Value<string>(), 
                    PosterImage = json.SelectToken("Poster").Value<string>(), 
                    Plot = json.SelectToken("Plot").Value<string>() 
                }; 
     
                return movieDetails; 
            } 
     
            return new MovieDetailModel 
            { 
                Title = movieName 
            }; 
        } 
    } 


In this class, we inject the HttpClient in our class constructor and set the base address of our OMDB API endpoint. We also implement GetMovieDetailsAsync method declared in our interface. We called the OMDB API from our method and mapped the API response to our view model and returned it. I have used the JSON.NET library for parsing the response from the API.
Note that I have hardcoded the API Base address and the API key in the code. This is not a healthy practice. We shall always prefer moving these kinds of values to configuration files and shall read those values from the configuration files in our code.
 
We have now created our typed client. Now let's create the view for user interaction. In the Index.cshtml file in the Views\Home folder, replace the existing code with the following code.
    @{ 
        ViewData["Title"] = "Home Page"; 
    } 
     
    @model MovieDetailModel 
     
    <form  
        asp-controller="Home"  
        asp-action="Submit" 
        method="post"  
        class="form-horizontal"  
        role="form"> 
     
        <div class="form-group"> 
            <label for="Title">Title</label> 
            <input  
                class="form-control"  
                placeholder="Enter Title" 
                asp-for="Title">    
        </div> 
        <button type="submit" class="btn btn-primary">Submit</button> 
    </form> 
     
     
    @{ 
        <br> 
         
        if(!string.IsNullOrEmpty(Model?.Year)) 
        { 
            var title = Model.Title; 
            var year = Model.Year;   
            var message = $"{title} is release on {year}";  
      
            <br> 
            <div class="card" style="width: 18rem;"> 
                <img class="card-img-top" [email protected] alt="Poster Not Available"> 
                <div class="card-body"> 
                    <h5 class="card-title">@Model.Title (@Model.Year)</h5> 
                    <p class="card-text">@Model.Plot</p>               
                </div> 
                <ul class="list-group list-group-flush"> 
                    <li class="list-group-item"><strong>Director : @Model.Director</strong></li> 
                    <li class="list-group-item"><strong>Actors : @Model.Actors</strong></li> 
                    <li class="list-group-item"><strong>Rating : @Model.IMDBRating</strong></li> 
                </ul> 
            </div>      
        } 
         
        if(Model != null && string.IsNullOrEmpty(Model?.Year)) 
        { 
            <div class="alert alert-danger" role="alert"> 
                <strong>Sorry!! Requested Movie Details are not available..</strong>  
            </div> 
        } 
    } 


Now, we need to add the Controller code for accepting the movie name from the view and for displaying the movie details. For that, we need to inject the typed client we had created into the constructor of our controller. So, we need to register this typed client with the HttpClient factory in our Startup.cs class. Add the following code in the ConfigureServices method in the startup class.
    services.AddHttpClient<IMovieDetailsClient, MovieDetailsClient>(); 

Now, let's add our controller methods. In the HomeController make the changes as below.
    public class HomeController : Controller 
    { 
        private readonly IMovieDetailsClient _movieDetailsClient; 
     
        public HomeController(IMovieDetailsClient movieDetailsClient) 
        { 
            _movieDetailsClient = movieDetailsClient; 
        } 
     
        public IActionResult Index() 
        { 
            return View(); 
        } 
     
        [HttpPost] 
        public async Task<IActionResult> Submit(MovieDetailModel model) 
        { 
            var movieDetail = await _movieDetailsClient.GetMovieDetailsAsync(model.Title); 
            return View("Index", movieDetail); 
        }       
    } 


We are injecting our IMovieDetails client in the constructor of our controller and assigning it to a read-only field _movieDetailsClient. We have also defined an action named Submit which takes the title of the movie from the view as a parameter. This method makes use of our typed HttpClient to fetch the details of that movie and shall return the view with the details of that movie.

Now, run the application. Execute the command dotnet run in the terminal. Open a browser and navigate to https://localhost:5001/.

 



European ASP.NET Core Hosting :: RESTful WebAPI With Onion Architecture

clock April 9, 2019 11:29 by author Peter

Hello friends, here I will show you how to create a WebApi with the following characteristics:

  • ASP.Core 2.1
  • EntityFramework
  • FluentValidation
  • Nlogger
  • Swagger
  • Jwt

Let's start. First create an empty project, then add the following folders:

  • Application
  • Domain
  • Service
  • Infrastructure

Then in the Domain folder, we create a library project Net.Core 2.1 with the name WebApi.Domain add the following dependencies

FluentValidation.AspNetCore

In this project, we add the following folders:

  • Dtos
  • Entities
  • Interfaces

In the Entities folder, we create the BaseEntity class:

    namespace WebApi.Domain.Entities 
    { 
        public abstract class BaseEntity 
        { 
            public virtual int Id { get; set; } 
        } 
    } 

Our project classes will inherit the field Id from this abstract class (if you want you can add other fields like CreatedAt or CreatedBy).

Then we create the Country class with the properties that defines a Country.

    namespace WebApi.Domain.Entities 
    { 
        public class Country : BaseEntity 
        { 
            public string Name { get; set; } 
            public int Population { get; set; } 
            public decimal Area { get; set; } 
            public string ISO3166 { get; set; } 
            public string DrivingSide { get; set; } 
            public string Capital { get; set; } 
     
        } 
    } 

Now to make the exercise more interesting, we are going to assume that we do not want to expose all the Country class. In the Dtos folder, we create the following CountryDensityDTO class.

    using System; 
    using WebApi.Domain.Entities; 
     
    namespace WebApi.Domain.Dtos 
    { 
        public class CountryDensityDTO : BaseEntity 
        { 
            public string Name { get; set; } 
            public string Capital { get; set; } 
            public decimal Area { get; set; } 
            public int Population { get; set; } 
     
     
            public int Populationdensity 
            { 
                get 
                { 
                    return Decimal.ToInt32(Population / Area); 
                } 
            } 
        } 
    }

This class exposes Name, Capital Area, Population and a calculated field Populationdensity.
Now we will continue with the Infrastructure layer and then we will finish the missing parts.We go to the Infrastructure folder and create a library Net.Core 2.1. We name it WebApi.Infrastructure.Data.

We add the following Packages:

  • Microsoft.EntityFrameworkCore.SqlServer 2.1.4
  • Microsoft.EntityFrameworkCore.Tools 2.1.4
  • Microsoft.Extensions.Identity.Stores 2.1.1
  • Microsoft.VisualStudio.Web.CodeGeneration.Design 2.1.5
  • Add Project reference WebApi.Domain 

We create the following folders:

  • Context
  • EntityDbMapping
  • Repository

In the Context Folder, we add the SqlServerContext class. We refer to our Country entity with DbSet to work with the database. As we work with CodeFirst approach, we will create a mapping for our entity Country in the database.
"modelBuilder.Entity<Country>(new CountryMap().Configure);"

Optionally, in this part we can also add seed data when creating a table.

using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 
using Microsoft.EntityFrameworkCore; 
using WebApi.Domain.Entities; 
using WebApi.Infrastructure.Data.EntityDbMapping; 

namespace WebApi.Infrastructure.Data.Context 

public class SqlServerContext :   IdentityDbContext<ApplicationUser> 

    public DbSet<Country> Country { get; set; } 

    public SqlServerContext(DbContextOptions<SqlServerContext> options) : base(options) 
    { 
      
    }     
    protected override void OnModelCreating(ModelBuilder modelBuilder) 
    { 
        base.OnModelCreating(modelBuilder); 
        modelBuilder.Entity<Country>(new CountryMap().Configure); 
        // ModelBuilderExtensions.Seed(modelBuilder); 

    } 

//Data for first time on table 
public static class ModelBuilderExtensions 

    public static void Seed(this ModelBuilder modelBuilder) 
    { 
        modelBuilder.Entity<Country>().HasData( 
            new Country 
            { 
                Id = 1, 
                Name = "Venezuela", 
                Population = 300000000, 
                Area = 230103 
              
            }, 
            new Country 
            { 
                Id = 2, 
                Name = "Peru", 
                Population = 260000000, 
                Area =33249               
            } 
        ); 
    } 


In the folder, EntityDbMapping, we create the CountryMap class. In this class, we define the physical representation of the properties of the Country class as fields in the table of the database.

    using Microsoft.EntityFrameworkCore;   
    using Microsoft.EntityFrameworkCore.Metadata.Builders;   
    using WebApi.Domain.Entities;   
       
    namespace WebApi.Infrastructure.Data.EntityDbMapping   
    {   
        public class CountryMap : IEntityTypeConfiguration<Country>   
        {   
            public void Configure(EntityTypeBuilder<Country> builder)   
            {   
                builder.ToTable("Country");   
       
                builder.HasKey(c => c.Id);   
       
                builder.Property(c => c.Name)   
                    .IsRequired()   
                    .HasColumnName("Name")   
                    .HasColumnType("varchar(150)");   
       
                builder.Property(c => c.Population)   
                    .IsRequired()   
                    .HasColumnType("int")   
                    .HasColumnName("Population");   
       
                builder.Property(c => c.Area)   
                    .IsRequired()   
                    .HasColumnType("decimal(14,2)")   
                    .HasColumnName("Area");   
       
                builder.Property(c => c.ISO3166)   
                .IsRequired()   
                .HasColumnType("varchar(3)")   
                .HasColumnName("ISO3166");   
       
                builder.Property(c => c.DrivingSide)   
                .IsRequired()   
                .HasColumnType("varchar(50)")   
                .HasColumnName("DrivingSide");   
       
                builder.Property(c => c.Capital)   
                .IsRequired()   
                .HasColumnType("varchar(50)")   
                .HasColumnName("Capital");   
            }   
       
        }   
    }   


This is it for now.
In the next chapter, we will implement validations with FluentValidation. We will also configure Mapper to use it with our DTOs and will implement Identity using Jwt.



European ASP.NET Core Hosting :: Consuming RabbitMQ Messages In ASP.NET Core

clock March 26, 2019 11:42 by author Peter

Background tasks play a very important role when we are building a distributed system. The most common scenario is consuming the service bus's message. In this article, I'd like to present how to consume the RabbitMQ message via BackgroundService in ASP.NET Core.
Run RabbitMQ Host

We should set up an instance of RabbitMQ. The fastest way is to use Docker.
docker run -p 5672:5672 -p 15672:15672 rabbitmq:management  

After running the Docker container, we are able to view the management page via http://localhost:15672.

Consuming RabbitMQ Messages In ASP.NET Core
Setup the BackgroundService
Here, we create a new class named ConsumeRabbitMQHostedService that is inherited from BackgroundService.
BackgroundService is a base class for implementing a long-running IHostedService. It provides the main work needed to set up the background task.
Here is an example to demonstrate how to consume RabbitMQ messages.
public class ConsumeRabbitMQHostedService : BackgroundService 

    private readonly ILogger _logger; 
    private IConnection _connection; 
    private IModel _channel; 
 
    public ConsumeRabbitMQHostedService(ILoggerFactory loggerFactory) 
    { 
        this._logger = loggerFactory.CreateLogger<ConsumeRabbitMQHostedService>(); 
        InitRabbitMQ(); 
    } 
 
    private void InitRabbitMQ() 
    { 
        var factory = new ConnectionFactory { HostName = "localhost" }; 
 
        // create connection 
        _connection = factory.CreateConnection(); 
 
        // create channel 
        _channel = _connection.CreateModel(); 
 
        _channel.ExchangeDeclare("demo.exchange", ExchangeType.Topic); 
        _channel.QueueDeclare("demo.queue.log", false, false, false, null); 
        _channel.QueueBind("demo.queue.log", "demo.exchange", "demo.queue.*", null); 
        _channel.BasicQos(0, 1, false); 
 
        _connection.ConnectionShutdown += RabbitMQ_ConnectionShutdown; 
    } 
 
    protected override Task ExecuteAsync(CancellationToken stoppingToken) 
    { 
        stoppingToken.ThrowIfCancellationRequested(); 
 
        var consumer = new EventingBasicConsumer(_channel); 
        consumer.Received += (ch, ea) => 
        { 
            // received message 
            var content = System.Text.Encoding.UTF8.GetString(ea.Body); 
 
            // handle the received message 
            HandleMessage(content); 
            _channel.BasicAck(ea.DeliveryTag, false); 
        }; 
 
        consumer.Shutdown += OnConsumerShutdown; 
        consumer.Registered += OnConsumerRegistered; 
        consumer.Unregistered += OnConsumerUnregistered; 
        consumer.ConsumerCancelled += OnConsumerConsumerCancelled; 
 
        _channel.BasicConsume("demo.queue.log", false, consumer); 
        return Task.CompletedTask; 
    } 
 
    private void HandleMessage(string content) 
    { 
        // we just print this message  
        _logger.LogInformation($"consumer received {content}"); 
    } 
     
    private void OnConsumerConsumerCancelled(object sender, ConsumerEventArgs e)  {  } 
    private void OnConsumerUnregistered(object sender, ConsumerEventArgs e) {  } 
    private void OnConsumerRegistered(object sender, ConsumerEventArgs e) {  } 
    private void OnConsumerShutdown(object sender, ShutdownEventArgs e) {  } 
    private void RabbitMQ_ConnectionShutdown(object sender, ShutdownEventArgs e)  {  } 
 
    public override void Dispose() 
    { 
        _channel.Close(); 
        _connection.Close(); 
        base.Dispose(); 
    } 


Configure Services
We should configure this hosted service with the background task logic in ConfigureServices method.
public void ConfigureServices(IServiceCollection services) 

    // others ... 
     
    services.AddHostedService<ConsumeRabbitMQHostedService>(); 
}  

Result

After running this app, we may get the following output in the terminal.Turning to the Management UI of RabbitMQ, we find that it creates a new exchange and a new queue.

The next time we try to publish a message to show the background task is running well, we get the following result.




European ASP.NET Core Hosting :: Using Docker to Store ASP.NET Core Kestrel Certificates

clock March 22, 2019 08:48 by author Scott

When working with ASP.Net Core in Docker containers, it can be cumbersome to deal with certificates. While there is a documentation about setting certificate for dev environment, there’s no real guidance on how to make it work when deploying containers in a Swarm for example.
In this article we are going to see how to take advantage of Docker secrets to store ASP.Net Core Kestrel certificates in the context of Docker Swarm.

Hosting the service

First of all, we are going to create à Swarm service on our machine that use the sample Asp.Net Core app. The purpose of this article is to make SSL work in the container withoutchanging anything to an existing image.

docker service create --name mywebsite --publish published=8080,target=80,mode=host microsoft/dotnet-samples:aspnetapp

We are creating service mywebsite, publishing only one port 8080 bound to the port 80 in the container using the host mode and using the image microsoft/dotnet-samples:aspnetapp. Please note that you can use others configuration (for example expose port in routing mesh mode).

Preparing the certificate

We need a certificate. It can be created via an external certificate authority but here for the sake of the article, we are going to create a self signed certificate (of course, don’t use this in production). We are using Powershell for this task (you can skip this if you already have a pfx certificate signed by a real CA).

$cert = New-SelfSignedCertificate -DnsName "mywebsite" -CertStoreLocation "cert:\LocalMachine\My"
$password = ConvertTo-SecureString -String "mylittlesecret" -Force -AsPlainText
$cert | Export-PfxCertificate -FilePath c:\temp\mywebsite.pfx -Password $password

Once you have your pfx, we are goind to unprotect it from the password. It might be seem unsecure but when it will be added to the Docker secret store, it will be stored securedly. For this task I will use OpenSSL (not possible with Powerhsell as far as I know). OpenSSL is provided with Git for example.

& 'C:\Program Files\Git\mingw64\bin\openssl.exe' pkcs12 -in c:\temp\mywebsite.pfx -nodes -out c:\temp\mywebsite.pem -passin pass:mylittlesecret
& 'C:\Program Files\Git\mingw64\bin\openssl.exe' pkcs12 -export -in c:\temp\mywebsite.pem -out c:\temp\mywebsite.unprotected.pfx -passout pass:

Now that the pfx is un protected, we can add it to the docker store certificate and display it.

docker secret create kestrelcertificate c:\temp\mywebsite.unprotected.pfx

docker secret ls

ID                          NAME                 DRIVER              CREATED             UPDATED

iapy6rolt7po1mwm9aw6z0qc5   kestrelcertificate                       13 minutes ago      13 minutes ago

Our secret being in the store, you can delete (or store securely somewhere else your pfx).

Making it work

We can now update our service to take in account this secret. When adding a secret to a service, Docker will create a file in a specific directory containing the value of the secret. On Windows it’s c:\programdata\docker\secrets.

Let’s update our service and see what happened inside the container.

docker service update --secret-add kestrelcertificate mywebsite

docker exec 4b51e736ce65 cmd.exe /c dir c:\programdata\docker\secrets

 Volume in drive C has no label.
 Volume Serial Number is 3CBB-E577

 Directory of c:\programdata\docker\secrets

11/15/2018  10:38 PM    <DIR>          .
11/15/2018  10:38 PM    <DIR>          ..
11/15/2018  10:38 PM    <SYMLINK>      kestrelcertificate [C:\ProgramData\Docker\internal\secrets\iapy6rolt7po1mwm9aw6z0qc5]
               1 File(s)              0 bytes
               2 Dir(s)  21,245,009,920 bytes free

We can see that our secret exists and is named kestrelcertificate, as we named it in the command line.

We can therefore update our service to remove the old binding on port 80, replace it with a binding on port 443, tell Kestrel to use this port and finally give Kestrel the path of our secret.
This can be done with only one command:

docker service update --publish-rm published=8080,target=80,mode=host --publish-add published=8080,target=443,mode=host --env-add ASPNETCORE_URLS=https://+:443 --env-add Kestrel__Certificates__Default__Path=c:\programdata\docker\secrets\kestrelcertificate mywebsite

Wait a while that your service update, try to browse and it should work ! Well, actually it should only works on Linux.

Making it work on Windows

If you try to have a look a the logs generated by your service, you should end with something like this.

docker service logs mywebsite

mywebsite.1.uy3vm8txwxec@nmarchand-lt    | crit: Microsoft.AspNetCore.Server.Kestrel[0]
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |       Unable to start Kestrel.
mywebsite.1.uy3vm8txwxec@nmarchand-lt    | Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Unspecified error
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String fileName, String password)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.LoadCertificate(CertificateConfig certInfo, String endpointName)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.LoadDefaultCert(ConfigurationReader configReader)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.ValidateOptions()
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |
mywebsite.1.uy3vm8txwxec@nmarchand-lt    | Unhandled Exception: Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Unspecified error
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String fileName, String password)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.LoadCertificate(CertificateConfig certInfo, String endpointName)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.LoadDefaultCert(ConfigurationReader configReader)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.KestrelConfigurationLoader.Load()
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.ValidateOptions()
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Hosting.Internal.WebHost.StartAsync(CancellationToken cancellationToken)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token, String shutdownMessage)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Hosting.WebHostExtensions.RunAsync(IWebHost host, CancellationToken token)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at Microsoft.AspNetCore.Hosting.WebHostExtensions.Run(IWebHost host)
mywebsite.1.uy3vm8txwxec@nmarchand-lt    |    at aspnetapp.Program.Main(String[] args) in C:\app\aspnetapp\Program.cs:line 18

We can see a nasty bug of Windows here (Github issue).
What did happen ? If you look closely at the dir command we made in the container, you’ll see that the secret is not really a file but instead a symbolic link to an other file. Unfortunately, Windows is unable to use a certificate that is a symlink. One solution could be to manually read the certificate with File.ReadAllBytes() and pass it to the constructor of X509Certificate. However, it would be against the purpose of this article which is to not modify the Docker image.

We can find a workaround by browsing the Docker documentation which states that the real file containing the secret (which in fact is the target of the symlink) can be found in the path c:\programdata\docker\internal\secrets\<secretid> where secretid is the id of the secret (as shown by docker secret ls).

We can update our service to change the path by updating the environment variable. It now works also on windows!

docker service update --env-rm
Kestrel__Certificates__Default__Path=c:\programdata\docker\secrets\kestrelcertificate --env-add Kestrel__Certificates__Default__Path=c:\programdata\docker\internal\secrets\iapy6rolt7po1mwm9aw6z0qc5 mywebsite

docker logs 7b54cdc42a86

Hosting environment: Production
Content root path: C:\app
Now listening on: https://[::]:443
Application started. Press Ctrl+C to shut down.

Final word

We have seen in this article how to use Docker secrets to store ASP.Net Core Kestrel certificates in our Docker Swarm. However, please keep in mind that the Windows workaround should be used with care as written in the Docker documentation.

Another word also about SSL Offloading : I know that usually the reverse proxy (Nginx, Traefik, etc.) is used to be the SSL termination but sometimes you still want SSL end to end. 



About HostForLIFE.eu

HostForLIFE.eu is European Windows Hosting Provider which focuses on Windows Platform only. We deliver on-demand hosting solutions including Shared hosting, Reseller Hosting, Cloud Hosting, Dedicated Servers, and IT as a Service for companies of all sizes.

We have offered the latest Windows 2016 Hosting, ASP.NET Core 2.2.1 Hosting, ASP.NET MVC 6 Hosting and SQL 2017 Hosting.


Tag cloud

Sign in