简体   繁体   中英

ASP.NET Core kestrel windows authentication in docker identifies wrong user

I'm using docker (windows nanoserver), traefik, asp.net core 3.1 and windows authentication/negotiate. If a user connects to the container, he or she logs in and their user is bound to the httpcontext. If another user connects afte that, the first user is still bound to the context.

This is my setup in code:

docker-compose.yml

version: "3.7"
services:
  webapplication:
    image: <repository>/webapplication:8-1
    networks:
      webapplication-network:
        aliases:
          - webapplication
      traefik:      
        aliases:
          - traefik-webapplication
    credential_spec:  
      file: webapp({GMSA_ACCOUNT})_credential.json
    secrets:
      - source: config_secrets
        target: C:/app/appsettings.json
    deploy:
      mode: replicated
      replicas: 1
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 10
        window: 30s
      labels: 
        - applicationname=({APPLICATIONNAME})
        - "traefik.enable=true"
        - "traefik.http.routers.({APPLICATIONNAME})-webapplication.rule=Host(`({APPLICATIONNAME})-webapplication.({DOMAIN})`)"
        - "traefik.http.routers.({APPLICATIONNAME})-webapplication.service=({APPLICATIONNAME})-webapplication"
        - "traefik.http.routers.({APPLICATIONNAME})-webapplication.entrypoints=https"
        - "traefik.http.routers.({APPLICATIONNAME})-webapplication.tls=true"
        - "traefik.http.services.({APPLICATIONNAME})-webapplication.loadbalancer.server.scheme=http"
        - "traefik.http.services.({APPLICATIONNAME})-webapplication.loadbalancer.server.port=80"

        # redirect http to https
        - traefik.http.middlewares.({APPLICATIONNAME})-webapplication-unsecure-redirect-secure.redirectscheme.scheme=https
        - traefik.http.routers.({APPLICATIONNAME})-webapplication-unsecure.middlewares=({APPLICATIONNAME})-webapplication-unsecure-redirect-secure
        - traefik.http.routers.({APPLICATIONNAME})-webapplication-unsecure.rule=Host(`({APPLICATIONNAME})-webapplication.({DOMAIN})`)
        - traefik.http.routers.({APPLICATIONNAME})-webapplication-unsecure.entrypoints=http

        # Attempt to make Windows authentication work through TCP
        - "traefik.tcp.routers.({APPLICATIONNAME})-webapplication.rule=HostSNI(`({APPLICATIONNAME})-webapplication.({DOMAIN})`)"
        - "traefik.tcp.routers.({APPLICATIONNAME})-webapplication.service=({APPLICATIONNAME})-webapplication"
        - "traefik.tcp.routers.({APPLICATIONNAME})-webapplication.tls=true"
        - "traefik.tcp.services.({APPLICATIONNAME})-webapplication.loadbalancer.server.port=80"         
        - "traefik.http.services.({APPLICATIONNAME})-webapplication.loadbalancer.sticky=true"

Dockerfile webapplication-windows-netcore-base

# escape=`
FROM mcr.microsoft.com/windows/servercore:ltsc2019 AS installer
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# Retrieve .NET Core Runtime
RUN $dotnet_version = '3.1.3'; `
    Invoke-WebRequest -OutFile dotnet.zip https://dotnetcli.azureedge.net/dotnet/Runtime/$dotnet_version/dotnet-runtime-$dotnet_version-win-x64.zip; `
    $dotnet_sha512 = '62a18838664afd6f08cdb9a90a96a67626743aab1f0de0065eadfd7d1df31681c90f96744ccb5b7e40834c9e3c4952125c8c83625867c500692050cdd113ff50'; `
    if ((Get-FileHash dotnet.zip -Algorithm sha512).Hash -ne $dotnet_sha512) { `
        Write-Host 'CHECKSUM VERIFICATION FAILED!'; `
        exit 1; `
    }; `
    `
    Expand-Archive dotnet.zip -DestinationPath dotnet; `
    Remove-Item -Force dotnet.zip; `
# Install ASP.NET Core Runtime
    $aspnetcore_version = '3.1.3'; `
    Invoke-WebRequest -OutFile aspnetcore.zip https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/$aspnetcore_version/aspnetcore-runtime-$aspnetcore_version-win-x64.zip; `
    $aspnetcore_sha512 = '6d8a21a7420db9091fc05613ef0a923c7b86cc995360c9f0133d632020e042db0efac0343ee6516a28feb2c1dd004b33b74bfdcc1a687efdefa9db1a486c1ca2'; `
    if ((Get-FileHash aspnetcore.zip -Algorithm sha512).Hash -ne $aspnetcore_sha512) { `
        Write-Host 'CHECKSUM VERIFICATION FAILED!'; `
        exit 1; `
    }; `
    `
    Expand-Archive aspnetcore.zip -DestinationPath dotnet -Force; `
    Remove-Item -Force aspnetcore.zip
# Runtime image
FROM mcr.microsoft.com/windows/servercore:ltsc2019

ENV `
    # Configure web servers to bind to port 80 when present
    ASPNETCORE_URLS=http://+:80 `
    # Enable detection of running in a container
    DOTNET_RUNNING_IN_CONTAINER=true

WORKDIR C:\app
USER ContainerAdministrator
RUN setx /M PATH "%PATH%;C:\Program Files\dotnet" && `
    REG ADD HKLM\SYSTEM\CurrentControlSet\Control\FileSystem /v LongPathsEnabled /t REG_DWORD /d 1 -f
COPY --from=installer ["/dotnet", "/Program Files/dotnet"]

Dockerfile webapplication

# escape=`
##### Runtime #####
ARG baseImageVersie
ARG buildNumber
ARG release
FROM <repository>/webapplication-build:$release-$buildNumber AS buildtools
FROM webapplication-windows-netcore-base:$baseImageVersie 
ENV DOTNET_RUNNING_IN_CONTAINER=true
ENTRYPOINT ["dotnet", "webapplication.dll"]
COPY --from=buildtools C:/publish .

Startup.cs looks like this

public class Startup
{
    private readonly IConfiguration configuration;

    public Startup(IConfiguration configuration)
    {
        this.configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers(o =>
        {
            o.Filters.Add(typeof(GlobalExceptionFilter));
        });
        services
            .AddAuthentication(NegotiateDefaults.AuthenticationScheme)
            .AddNegotiate(o => o.Validate());

        var appsettings = configuration.Get<Appsettings>();

        services.AddCors(options =>
        {
            options.AddPolicy("cors",
            builder =>
            {
                builder.WithOrigins(appsettings.Origins)
                    .SetIsOriginAllowedToAllowWildcardSubdomains()
                    .AllowAnyHeader()
                    .AllowAnyMethod()
                    .AllowCredentials();
            });
        });

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { 
                Title = "Webapplication", 
                Version = "v1" ,
            });
            c.ExampleFilters();

            var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
            var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
            c.IncludeXmlComments(xmlPath); 
            c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>(); 
        });

        services.AddSwaggerExamplesFromAssemblyOf<AuthorizeModelExample>();

    }

    // ReSharper disable once UnusedMember.Global
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();

        app.UseCors("cors");
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints
                .MapControllers()
                .RequireAuthorization()
                .RequireCors("cors");
        });
        app.UseSwagger();
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "Webapplication V1");
        });
    }
}

UserController

[Produces("application/json")]
[ApiController]
[Route("api/connect/[controller]")]
public class UserController : ControllerBase
{
    [HttpGet]
    public ActionResult Get()
    {
        return Ok(User?.Identity?.Name)
    }
}

What happens - User 1 logs in (using incognito mode) by filling in his credentials - User 1 calls /api/connect/user and it returns "user 1" - User 2 opens webapplication and does not have to provide any credentials - User 2 calls /api/connect/user and it still returns "user 1"

Question: How do i prevent my asp.net core application returning the wrong windows authentication user?

I managed to make it work using traefik TLS passthrough. So I had to change the application to serve https itself instead of having traefik do SSL termination. My application's compose file now looks like this:

docker-compose.yml

version: "3.7"
services:
  webapplication:
    image: <repository>/webapplication:8-1
    environment:
      - ASPNETCORE_Kestrel__Certificates__Default__Path=wildcard_certificate.pfx
    networks:
      webapplication-network:
        aliases:
          - webapplication
      traefik:      
        aliases:
          - traefik-webapplication
    credential_spec:  
      file: webapp({GMSA_ACCOUNT})_credential.json
    secrets:
      - source: config_secrets
        target: C:/app/appsettings.json
      - source: wildcard_certificate_pfx
        target: c:\certificates\wildcard_certificate.pfx
    deploy:
      mode: replicated
      replicas: 1
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 10
        window: 30s
      labels: 
        - applicatienaam=({APPLICATIENAAM})
        - "traefik.enable=true"
        - "traefik.http.routers.({APPLICATIENAAM})-webapplication.rule=Host(`({APPLICATIENAAM})-webapplication.({DOMAIN})`)"
        - "traefik.http.routers.({APPLICATIENAAM})-webapplication.entrypoints=https"
        - "traefik.http.routers.({APPLICATIENAAM})-webapplication.tls=true"
        - "traefik.http.services.({APPLICATIENAAM})-webapplication.loadbalancer.server.scheme=https"
        - "traefik.http.services.({APPLICATIENAAM})-webapplication.loadbalancer.server.port=443"

        # Windows authentication works through TCP
        # TLS passtrough, because otherwise windows authentication won't support multiple users
        - "traefik.tcp.routers.({APPLICATIENAAM})-webapplication.tls=true"
        - "traefik.tcp.routers.({APPLICATIENAAM})-webapplication.tls.options=default"
        - "traefik.tcp.routers.({APPLICATIENAAM})-webapplication.tls.passthrough=true"
        - "traefik.tcp.routers.({APPLICATIENAAM})-webapplication.rule=HostSNI(`({APPLICATIENAAM})-webapplication.({DOMAIN})`)"
        - "traefik.tcp.routers.({APPLICATIENAAM})-webapplication.entrypoints=https"
        - "traefik.tcp.services.({APPLICATIENAAM})-webapplication.loadbalancer.server.port=443"

I think the first user that logs in is bound to the traefik connection, so all next users will use the first users session, but I don't know for sure. All I know is that the solution above prevents mixing up user sessions.

I also had to make a change to my dockerfile to make serving https from the container work, because of a WindowsCryptographicException bug .

# escape=`
##### Runtime #####
ARG baseImageVersie
ARG buildNumber
ARG release
FROM <repository>/webapplication-build:$release-$buildNumber AS buildtools
FROM webapplication-windows-netcore-base:$baseImageVersie 
ENV DOTNET_RUNNING_IN_CONTAINER=true

# The copy is done, because wildcard_certificate.pfx is put into the container using docker secrets, which makes it a symlink. 
# Reading a certificate as a symlink is not supported at this moment: https://stackoverflow.com/q/43955181/1608705
# After doing a copy, the copied version is not a symlink anymore.
ENTRYPOINT (IF EXIST "c:\certificates\wildcard_certificate.pfx" (copy c:\certificates\wildcard_certificate.pfx c:\app\wildcard_certificate.pfx)) && dotnet webapplication.dll

COPY --from=buildtools C:/publish .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM