ASP.NET core and integrated windows authentication in nanoserver container

 

 

Below is overview of steps required to use integrated Windows Authentication in ASP.NET core application inside nanoserver container.

Completion of this instructions will allow you to run nanoserver container in windows AD environment and authenticate users based on their Windows desktop credentials or externally by providing Windows username/password to AD environment. This setup also RBAC access using Windows Groups as explained below.

Entire project with source code located on following github repo (https://github.com/artisticcheese/ContainerWindowsAuth)

High level overview of steps required to complete this scenario

  1. Complete first 7 steps in following article (Enabling integrated Windows Authentication in windows docker container)
  2. Build docker container based off github repo above
  3. Launch docker container in AD environment with GMSA account (step 8 in Enabling integrated Windows Authentication in windows docker container)

Detailed instructions

 

Completion steps 1-7 from following article Enabling integrated Windows Authentication in windows docker container will prepare your AD environment to host windows container with integrated windows authentication.

Add following snippet to startup.cs of your .NET core project which will enabled integrated windows authentication to be used in .NET core

services.Configure<IISOptions>(options => {
options.AutomaticAuthentication = true;
});
services.AddAuthentication(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme);

view raw
a.cs
hosted with ❤ by GitHub

Add .UseIISIntegration() to your  BuildWebHost method of Program.cs

public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
}

view raw
a.cs
hosted with ❤ by GitHub

HomeController has 4 methods below. Windows requires authentication and allows only members Domain Admins, Users allow only Domain Users group, LocalUsers allow any account passed by Windows authentication and Anonymousis allowing to be accessed without authentication

[Authorize(Roles = "Domain Admins")]
public IActionResult Windows()
{
return Content ("You are " + User.Identity.Name + "; Authentication Type:" + User.Identity.AuthenticationType );
}
[Authorize(Roles = "Domain Users")]
public IActionResult Users()
{
return Content("You are " + User.Identity.Name + "; Authentication Type:" + User.Identity.AuthenticationType);
}
public IActionResult LocalUsers()
{
return Content("You are " + User.Identity.Name + "; Authentication Type:" + User.Identity.AuthenticationType);
}
[AllowAnonymous]
public IActionResult Anonymous()
{
return Content ("You are " + User.Identity.Name + "; Authentication Type:" + User.Identity.AuthenticationType );;
}

view raw
a.cs
hosted with ❤ by GitHub

So accessing URL on your running container at http://container/home/windows will require authentication and http://container/home/anonymous is allowed to be accessed without authentication.

Multistage build is used to build .NET core project and put in resulting container along with all neccessary prerequisites (IIS and ASP.NET core). For detailed instructions you can read in this article (Using multistage docker build to create IIS + ASP.NET Core image + nanoserver).

Dockerfile for entire project is below. It shall be pretty self explanatory.

# escape=`
# Image with NET CORE installation to extract executables for final image
FROM microsoft/aspnetcore:2.0.0-nanoserver As CoreBuild
# Image with SDK to build .NET Core application from source
FROM microsoft/aspnetcore-build:2.0.0-nanoserver As builder
WORKDIR /source/
COPY source/iis .
RUN dotnet restore; dotnet publish -c Release
# Middleware image used to extract ASP.NET core module
From microsoft/iis as WindowsBuild
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'Continue'; $verbosePreference='Continue';"]
# Installing NET CORE webhosting in middleware image so latest module and configuration schema is extracted for final image
ADD https://download.microsoft.com/download/B/1/D/B1D7D5BF-3920-47AA-94BD-7A6E48822F18/DotNetCore.2.0.0-WindowsHosting.exe ".\hosting.exe"
RUN start-process -Filepath .\hosting.exe -ArgumentList @('/install', '/quiet', '/norestart') -Wait
FROM microsoft/iis:nanoserver
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'Continue'; $verbosePreference='Continue';"]
RUN start-process -Filepath dism.exe -ArgumentList @('/online', '/enable-feature:IIS-WindowsAuthentication', '/ALL') -Wait
# Adding NET CORE EXE to Path environment variable
RUN setx /M PATH $($Env:PATH + ';' + $Env:ProgramFiles + '\dotnet')
#Copy dotnet core installation from middleware image
COPY –from=CoreBuild ["c:\\Program Files\\dotnet", "c:\\program files\\dotnet"]
#Copy dotnet core module from middleware image
COPY –from=WindowsBuild ["c:\\Windows\\System32\\inetsrv\\aspnetcore.dll", "c:\\Windows\\System32\\inetsrv\\"]
COPY –from=WindowsBuild ["c:\\Windows\\System32\\inetsrv\\config\\schema\\aspnetcore_schema.xml", "c:\\Windows\\System32\\inetsrv\\config\\schema\\"]
WORKDIR app
# Configure IIS to use ASPNET core module
RUN Import-Module IISAdministration; `
Start-IISCommitDelay; `
(Get-IISServerManager).GetApplicationHostConfiguration().RootSectionGroup.Sections.Add('appSettings') ; `
(Get-IISServerManager).GetApplicationHostConfiguration().GetSection('system.webServer/handlers').Overridemode = 'Allow' ; `
(Get-IISServerManager).GetApplicationHostConfiguration().RootSectionGroup.SectionGroups['system.webServer'].Sections.Add('aspNetCore'); `
(Get-IISServerManager).GetApplicationHostConfiguration().RootSectionGroup.SectionGroups['system.webServer'].Sections['aspNetCore'].OverrideModeDefault = 'Allow'; `
New-IISConfigCollectionElement (Get-IISConfigSection 'system.webServer/globalModules' | Get-IISConfigCollection) -ConfigAttribute @{'name'='AspNetCoreModule';'image'='C:\windows\system32\inetsrv\aspnetcore.dll'}; `
New-IISConfigCollectionElement (Get-IISConfigSection 'system.webServer/modules' | Get-IISConfigCollection) -ConfigAttribute @{'name'='AspNetCoreModule'}; `
Stop-IISCommitDelay
# Add local user for testing authentication against local SAM database
RUN new-LocalUser -Name "ContainerAdmin" -Password (ConvertTo-SecureString "A123456!" -AsPlainText -Force); `
Add-LocalGroupMember -Group Administrators -Member "ContainerAdmin" ;
# Dot net core Application to show logged on user
RUN Import-Module IISAdministration; Start-IISCommitDelay; (Get-IISServerManager).ApplicationPools['DefaultAppPool'].ProcessModel.IdentityType='LocalSystem'; `
(Get-IISServerManager).Sites[0].Applications[0].VirtualDirectories[0].PhysicalPath = 'c:\app'; (Get-IISConfigSection -SectionPath 'system.webServer/security/authentication/windowsAuthentication').Attributes['enabled'].value = $true; `
Stop-IISCommitDelay
# Copying compiled file from middleware NET CORE builder SDK image
COPY –from=builder /source/bin/Release/netcoreapp2.0/publish .
EXPOSE 80

view raw
Dockerfile
hosted with ❤ by GitHub

Building final image puts together container image consisting of 3 intermediate images used to:

  • Build ASP.NET core project
  • Copy ASP.NET executables to final image
  • Install ASP.NET IIS module to servercore image and copy it over to final image

Once image is built you can deploy to production container host which was prepared earlier in step 1 above.

Launch docker image with following parameters

docker run -d -h containerhost --security-opt "credentialspec=file://win.json" -p 88:80 artisticcheese/winauth:nano-iis

Parameters specify which GMSA account is being used to authenticate to AD and location of JSON file with details.

Accessing URL http://containerhost:88/Home/Windows on container host from domain joined client will automatically log you with current user credentials with no prompt like below. While accessing http://containerhost:88/Home/Anonymous will allow you to access it without username/password.

Accessing with user account which is not part of Domain Admins will return HTTP 403 on /home/windows as expected but will succeed on /home/users

See screenshots below. If you don’t want to automatically log in with default credentials you can access page via IP address which will prevent IE/Chrome from using them. http://192.168.1.235:88/Home/Windows, please note that your authentication switches to NTLM instead of Kerberos authentication in such case.

To check local user hardcoded into image you can go to /Home/LocalUsers and user ContainerAdminand password A123456!

vmconnect_2017-09-13_11-48-14

8 thoughts on “ASP.NET core and integrated windows authentication in nanoserver container

  1. I really enjoyed reading your posts about AD integration of ASP.NET and ASP.NET Core apps in Docker! We’re in the process of moving our ASP.NET Core app to Docker, but we’re running into issues with Windows authentication. Following your guide and the documentation at GitHub, I’ve managed to successfully authenticate clients from a domain-joined host, but then something strange happens: it seems there can only be 1 active session in the whole app!

    The domain-joined host is used for testing purposes, our app shows the currently logged in domain user. If I start 2 parallel mstsc sessions for 2 different users to the domain-joined hosts, both are authenticated using their Kerberos tickets but the last one to ‘log in’ to the app will determine what the other session’s username will show when the browser window is refreshed. Also, when accessing the app from a non-domain-joined host, there seems to be no authentication needed: the same username as on the domain-joined hosts is displayed, as if the user session is cached in IIS and is shared by all clients.

    I’ve stumbled across this article: https://blogs.msdn.microsoft.com/benjaminperkins/2011/10/31/kerberos-authpersistnonntlm-authentication-request-based-vs-session-based-authentication/ If I turn this option off for Windows Authentication the app seems to respond better to authentication requests. Have you tried something similar? If you can, would you try out if you can reproduce this behavior?

    Also, when hosting the ASP.NET Core app in IIS, is it necessary to specify the IISOptions to the service, or is that only applicable if http.sys is used instead of IIS?

    Lastly, we’ve hit an issue when using routes.MapSpaFallbackRoute with a controller method with the [Authorize] attribute. It seems when falling back to NTLM (for non-domain joined hosts), the user session is not re-used for subsequent requests. The browser keeps asking for credentials for each separate HTTP request the browser makes. Have you encountered this before?

    Like

    1. I’m not a developer but systems administrator so code samples which I provided might be either incomplete or erroneous. Point is to show that you can get windows authentication working inside container which is not actually part of AD. So issues you are experience with caching etc probably due to my coding skills rather then underlying issues with enabled integrated authentication.

      Like

      1. Did you get to a bottom of this? I’m troubleshooting and trying to figure out if there is an issue but not seeing this behavior. To make sure you are not getting responses you shall not be expecting, use entirely different browser for second session (not just another Chrome window for example)

        Like

      2. Yes, it turns out Traefik (which we use to make multiple instances of our Docker application stack) is not properly forwarding the authentication headers to the clients. Once we took Traefik out of the equation the sessions (including SSO) worked properly.

        One thing we see is that under Chrome there are seemingly random moments where the browser asks for a username/password, even if the user has provided that information only minutes ago. It may be a caching issue, we haven’t investigated further on this behavior.

        Like

  2. I suggest to test this code in proper Windows 2016 image without docker and see if it’s the same issue, if it is then issue with code, if not then with docker and I can investigate further.

    Like

  3. Microsoft have appeared to have broken the “credentialspec” parameter on running windows containers through docker on windows 10. No updates as of yet still waiting on resolution as of windows 10 version 1803. Any alternative ways of getting this working would be appreciated.

    Many Thanks

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s