microsoft / microsoft/Windows-Containers

Kerberos broken in recent nanoserver images

Open
#537 25 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

🔖 ADO bug gMSA
Dominant language
PowerShell
Stars
551
Forks
76
PR merge metrics
No merged PRs in 30d

Description

Describe the bug
It looks like nanoserver-ltsc2022 images released in April 2024 and later have broken Kerberos.

To Reproduce
First, create a simple ASP.NET Core project AspNetKerbHello.

Create AspNetKerbHello.csproj with the following content:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Authentication.Negotiate" Version="8.0.8" />
  </ItemGroup>

</Project>

Now, create Program.cs with the following content - we are enabling negotiate authentication here:

using Microsoft.AspNetCore.Authentication.Negotiate;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
    .AddNegotiate();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

var app = builder.Build();

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

app.MapGet("/hello", (HttpContext context) =>
{
    var userName = context.User.Identity?.Name ?? "Anonymous";
    var authType = context.User.Identity?.AuthenticationType ?? "None";
    return Results.Ok($"Hello, {userName}! - via {authType}");
});

app.Run();

Now build the above project, using dotnet build command.

Now, inside the build output directory where AspNetKerbHello.dll is present, create a Dockerfile with the following content. We are purposefully pulling .NET runtime 8.0.4 because it uses nanoserver image from April 2024 - i.e. mcr.microsoft.com/windows/nanoserver:ltsc2022-KB5036909 -- but this works with even the latest nanoserver image:

FROM mcr.microsoft.com/dotnet/aspnet:8.0.4-nanoserver-ltsc2022
WORKDIR /app
EXPOSE 80
ENV ASPNETCORE_URLS=http://+:80

COPY . .

USER ContainerUser
ENTRYPOINT ["dotnet", "AspNetKerbHello.dll"]

Now build this docker image, let's assume it's tagged aspnetkerbhello:v1:

docker build -t aspnetkerbhello:v1 .

Now run this image in Kubernetes as a gMSA that owns a SPN

Now, access the Kubernetes SVC URI - assuming the SPN owned by the gMSA is http/<FQDN> where FQDN is Kubernetes SVC domain name. In the below example SPN is http/my-svc.my-namespace.svc.cluster.local:

Invoke-WebRequest -UseBasicParsing -UseDefaultCredentials -AllowUnencryptedAuthentication "http://my-svc.my-namespace.svc.cluster.local/hello"

Assuming NTLM is disabled and authentication is happening over Kerberos, you will get HTTP 500 error with an exception containing the following error callstack in the Kubernetes pod logs:

fail: Microsoft.AspNetCore.Authentication.Negotiate.NegotiateHandler[5]
      An exception occurred while processing the authentication request.
      System.Net.InternalException: Exception of type 'System.Net.InternalException' was thrown. -1073741428
         at System.Net.SecurityStatusAdapterPal.GetSecurityStatusPalFromInterop(SECURITY_STATUS win32SecurityStatus, Boolean attachException)
         at System.Net.NegotiateAuthenticationPal.WindowsNegotiateAuthenticationPal.AcceptSecurityContext(SafeFreeCredentials credentialsHandle, SafeDeleteContext& securityContext, ContextFlags requestedContextFlags, ReadOnlySpan`1 incomingBlob, ChannelBinding channelBinding, Byte[]& resultBlob, Int32& resultBlobLength, ContextFlags& contextFlags)
         at System.Net.NegotiateAuthenticationPal.WindowsNegotiateAuthenticationPal.GetOutgoingBlob(ReadOnlySpan`1 incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
         at System.Net.Security.NegotiateAuthentication.GetOutgoingBlob(ReadOnlySpan`1 incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
         at System.Net.Security.NegotiateAuthentication.GetOutgoingBlob(String incomingBlob, NegotiateAuthenticationStatusCode& statusCode)
         at Microsoft.AspNetCore.Authentication.Negotiate.NegotiateState.GetOutgoingBlob(String incomingBlob, BlobErrorType& status, Exception& error)
         at Microsoft.AspNetCore.Authentication.Negotiate.NegotiateHandler.HandleRequestAsync()

If you made a mistake somewhere, you will instead get authenticated over NTLM and won't see the error - rather a 200 OK page with the following message:

"Hello, contoso\\username! - via NTLM"

The issue only occurs when using Kerberos, not NTLM.

Now, all of the above can be also done without involving Kubernetes with a docker container running with gMSA cred-spec file but it's bit tricky - you need to first enable Kerberos on loopback using DisableLoopbackCheck registry key, and then use [System.Net.AuthenticationManager]::CustomTargetNameDictionary to map localhost to the SPN owned by the gMSA; and even then you might endup with NTLM. So I recommend using Kubernetes for testing the above.

You do not need multiple replicas. Even just a single pod runs into the above error.

I know the issue isn't because of any change in the .NET runtime version because I have created a custom .NET runtime image with the exact same runtime version (8.0.4) but older (March 2024) nanoserver base image and I couldn't repro the issue - suggesting the issue is in the nanoserver base image.

Expected behavior

Kerberos should work with nanoserver images

Configuration:

  • Edition: Windows Server 2022 with September 2024 build
  • Base Image being used: nanoserver-ltsc2022
  • Container engine: docker and containerd both
  • Container Engine version: docker 26 / containerd 1.6.31

Additional context

Per https://github.com/dotnet/runtime/discussions/105567#discussion-6980650 the error System.Net.InternalException: Exception of type 'System.Net.InternalException' was thrown. -1073741428 translates to ERROR_TRUSTED_DOMAIN_FAILURE or The trust relationship between the primary domain and the trusted domain failed, so I suspect this is likely related to the other gMSA issue https://github.com/microsoft/Windows-Containers/issues/405

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure with AspNetKerbHello.csproj, Program.cs, and the shown Dockerfile, then build and run the image in Kubernetes with a gMSA. Start at the Negotiate authentication request and compare the April 2024 or newer nanoserver base image with the older March 2024 image. Done means the /hello request succeeds over Kerberos instead of returning HTTP 500.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, docker, kubernetes
Domain
authentication, devops, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.