Running .NET 8 Azure Container Instance app as root user (SocketException permission denied when trying to bind port 80 or 443 to Kestrel with .NET 8)

Zoltán Lehóczky 50 Reputation points
2024-01-09T00:39:36.4633333+00:00

This issue happens with Linux Azure Container Instances (ACI) after upgrading the app to .NET 8; the exact same code works targeting .NET 6 (i.e. if I only change <TargetFramework> in the csproj and the publish profile).

I get the following exception when launching a .NET 8 ASP.NET Core app in an ACI:

fail: Microsoft.Extensions.Hosting.Internal.Host[11]
      Hosting failed to start
      System.Net.Sockets.SocketException (13): Permission denied
         at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
         at System.Net.Sockets.Socket.Bind(EndPoint localEP)
         at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint)
         at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
         at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<<StartAsync>g__OnBind|0>d.MoveNext()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.AnyIPListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(ListenOptions[] listenOptions, AddressBindContext context, Func`2 useHttps, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
         at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
         at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
Unhandled exception. System.Net.Sockets.SocketException (13): Permission denied
   at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
   at System.Net.Sockets.Socket.Bind(EndPoint localEP)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind()
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<<StartAsync>g__OnBind|0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.AnyIPListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(ListenOptions[] listenOptions, AddressBindContext context, Func`2 useHttps, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
   at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at Program.<Main>$(String[] args) in E:\MyProject.Web\Program.cs:line 42

This happens if I try to make Kestrel listen on ports 80, 443, or both. I don't get any more information with Trace-level logging.

The "New non-root 'app' user in Linux images" .NET 8 breaking changes looks like the issue behind this. Also see the blogpost it links. And sure enough, with .NET 6 the container's user is root, and with .NET 8 it's app.

However, I couldn't fix this. This is what I tried:

  • Using --run-as-group 0 --run-as-user 0 with az container create (as well as --allow-escalation) but nothing changed.
  • Using --privileged with az container create (as well as --allow-escalation) but nothing changed either.
  • Adding USER root in the Dockerfile doesn't do anything either because the Dockerfile has no effect for Azure Container Instance, they use az container create as a substitute.
  • I also tried setting ContainerUser with <ContainerUser>root</ContainerUser> but no luck.
  • Alternative configuration options for Kestrel, but it doesn't matter how I configure this, be it in appsettings.json under Kestrel__Endpoints, the ASPNETCORE_HTTP_PORTS and ASPNETCORE_HTTPS_PORTS environment variables, or with UseKestrel().
  • Making Kestrel listen under the internal IP of the container, not any IP, but that yields the same result.
  • Using different ports, like 8080 and 8443. That does work, but since I need this web app to be available under standard HTTP and HTTPS, 80 and 443 should be the ones publicly exposed. And I can't do that while using a different port internally, since ACI doesn't support port mapping.
  • I'm aware of the default port having changed from 80 to 8080 with .NET 8, however, that should be addressed by setting the port to 80 explicitly as under the first point, nor should I affect port 443.
  • ACI seems to reserve port 443 but neither should that affect port 80, nor should port 443 work with .NET 6. So, this is a .NET-related issue, not this port reservation.

I can't reproduce this locally under Windows but that's not too surprising.

The container image is published self-contained.

Can somebody help me understand what I may be doing wrong?

Azure Container Instances
Azure Container Instances
An Azure service that provides customers with a serverless container experience.
646 questions
{count} votes

Accepted answer
  1. kobulloc-MSFT 23,736 Reputation points Microsoft Employee
    2024-01-11T00:59:55.8233333+00:00

    Hello, @Zoltán Lehóczky!

    I'm glad that you were able to resolve your issue and thank you for posting your solution so that others experiencing the same thing can easily reference this! Since the Microsoft Q&A community has a policy that "The question author cannot accept their own answer. They can only accept answers by others", I'll repost your solution in case you'd like to "Accept" the answer.

    Issue:

    Running .NET 8 Azure Container Instance app as root user (SocketException permission denied when trying to bind port 80 or 443 to Kestrel with .NET 8)

    Solution (provided by Zoltán Lehóczky):

    I just found the solution. It was actually setting ContainerUser with <ContainerUser>root</ContainerUser> in the csproj. No other changes necessary.

    So, all in all, compared to the .NET 6 version, my app has the following changes to target .NET 8:

    • <TargetFramework>net8.0</TargetFramework> in the csproj.
    • <TargetFramework>net8.0</TargetFramework> in the pubxml used by az container create.
    • <ContainerUser>root</ContainerUser> in the csproj, see below the context.

    Full section of the csproj:

      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <UserSecretsId>MyId</UserSecretsId>
        <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
        <NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
        <EnableSdkContainerSupport>true</EnableSdkContainerSupport>
        <ContainerUser>root</ContainerUser>
      </PropertyGroup>
    

    I did try this before, but apparently, I messed up something during publishing, but now it's clear.

    I also opened an issue to clarify this in the docs: [https://github.com/dotnet/docs/issues/39082.


2 additional answers

Sort by: Most helpful
  1. Zoltán Lehóczky 50 Reputation points
    2024-01-10T20:11:30.57+00:00

    I just found the solution. It was actually setting ContainerUser with <ContainerUser>root</ContainerUser> in the csproj. No other changes necessary.

    So, all in all, compared to the .NET 6 version, my app has the following changes to target .NET 8:

    • <TargetFramework>net8.0</TargetFramework> in the csproj.
    • <TargetFramework>net8.0</TargetFramework> in the pubxml used by az container create.
    • <ContainerUser>root</ContainerUser> in the csproj, see below the context.

    Full section of the csproj:

      <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <UserSecretsId>MyId</UserSecretsId>
        <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
        <NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
        <EnableSdkContainerSupport>true</EnableSdkContainerSupport>
        <ContainerUser>root</ContainerUser>
      </PropertyGroup>
    

    I did try this before, but apparently, I messed up something during publishing, but now it's clear.

    I also opened an issue to clarify this in the docs: https://github.com/dotnet/docs/issues/39082.So thank you for your help @kobulloc-MSFT, this is now fixed.

    1 person found this answer helpful.

  2. kobulloc-MSFT 23,736 Reputation points Microsoft Employee
    2024-01-10T02:38:56.72+00:00

    Hello, @Zoltán Lehóczky!

    Why am I getting SocketException permission denied when trying to bind port 80 or 443 to Kestrel with .NET 8?

    This mirrors the other answer but the Microsoft .NET blog goes into more detail about a breaking change that was made with .NET 8 regarding container image ports. You are still able to use port 80 (and root) by re-defining ASPNETCORE_HTTP_PORTS in your Dockerfile or via the CLI:

    https://devblogs.microsoft.com/dotnet/securing-containers-with-rootless/

    (Apologies in advance for the formatting issues, please refer to the blog above)

    Switching to port 8080

    The biggest sticking point of the project was the ports that we expose. In fact, it is so much of a sticking point that we had to make a breaking change.

    We decided to standardize on port 8080 for all container images going forward. This decision was based on our earlier experience with Chiseled images, which already listen on port 8080. All the images now match.

    However, ASP.NET Core apps (using our .NET 7 and earlier container images) listen on port 80. The problem is that port 80 is a privileged port that requires root permission (at least in some places). That’s inherently incompatible with non-root containers.

    You can see how the ports are configured in our images.

    For .NET 8:

    $ docker run --rm mcr.microsoft.com/dotnet/aspnet:8.0-preview bash -c "export | grep ASPNETCORE"
    declare -x ASPNETCORE_HTTP_PORTS="8080"
    

    For .NET 7 (and earlier):

    $ docker run --rm mcr.microsoft.com/dotnet/aspnet:7.0 bash -c "export | grep ASPNETCORE"
    declare -x ASPNETCORE_URLS="http://+:80"
    

    Going forward, your port mapping will need to change.

    You can do this via the CLI. You’ll need 8080 on the right-hand of the mapping. The left-hand side can match or be another value.

    docker run --rm -it -p 8080:8080 aspnetapp
    

    Some users will want to continue to use port 80 (and root). You can still do that.

    You can re-define ASPNETCORE_HTTP_PORTS in your Dockerfile or via the CLI.

    For Dockerfile:

    ENV
    

    For Docker CLI:

    docker run --rm -e ASPNETCORE_HTTP_PORTS=80 -p 8000:80 aspnetapp
    

    .NET 8 Windows Container images use port 8080 as well.

    >docker run --rm mcr.microsoft.com/dotnet/aspnet:8.0-preview-nanoserver-ltsc2022 cmd /c "set | findstr ASPNETCORE"
    ASPNETCORE_HTTP_PORTS=8080
    

    ASPNETCORE_HTTP_PORTS is a new environment variable for specifying the port (or ports) for ASP.NET Core (actually, Kestrel) to listen on. It takes a semi-colon delimited list of port values. .NET 8 images use this new environment variable, instead of ASPNETCORE_URLS (which is used in .NET 6 and 7 images). ASPNETCORE_URLS remains a useful advanced feature. It enables specifying both raw HTTP and TLS ports in one configuration and overrides both ASPNETCORE_HTTP_PORTS and ASPNETCORE_HTTPS_PORTS.


    I hope this has been helpful! Your feedback is important so please take a moment to accept answers.

    If you still have questions, please let us know what is needed in the comments so the question can be answered. Thank you for helping to improve Microsoft Q&A!

    User's image

    0 comments No comments