SocketException permission denied when trying to bind port 80 or 443 to Kestrel in Azure Container Instance with .NET 8
Asked Answered
S

3

5

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 an ASP.NET Core 8 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.

This is what I tried:

  • 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 it 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.
  • Using --privileged with az container create (as well as --allow-escalation) but nothing changed.

I can't reproduce this locally under Windows.

The container image is published self-contained.

Can somebody help me understand what I may be doing wrong? Is this perhaps an undocumented breaking change, in .NET 8 or how ACI supports .NET 8?

Cross-post from an aspnetcore discussion since nobody replied there for a week.

Update: I cross-posted to Microsoft Q&A for visibility.

Update 2: I dug some more and found that actually this might be the breaking change relevant to this issue, not the default port change: New non-root 'app' user in Linux images. 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.

I tried

az container create `
    --run-as-group 0 `
    --run-as-user 0 `
...

to run the container as root but it still runs as app. 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.

Smoky answered 3/1 at 18:16 Comment(0)
S
5

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.

Smoky answered 10/1 at 20:9 Comment(0)
S
2

.NET 8 linux containers are not running under user "root" anymore. And because port 80 is a Privileged port, you can not make Kestrel listen on port 80.

I'm not familiar with Container Instances, but I think you can do port mapping as shown here. You can leave the default 8080 port for inside the container.

Stepsister answered 9/1 at 12:23 Comment(1)
Thank you for your reply! Docker Compose ACI integration that you linked to is not available anymore: docs.docker.com/cloud. It most possibly wouldn't have worked anyway, since as linked in my question, ACI doesn't support port mapping. I do need port 80 and 443 open though, since this is a web app. I can't put a reverse proxy in front of it since this app actually serves as a reverse proxy. I updated the question with what else I tried.Smoky
F
0

Ran into this issue deploying under an AWS ECS task, just noting that it's not isolated to Azure containers. Thank you for figuring out a solution!

Firebreak answered 13/6 at 17:53 Comment(1)
I'm glad to hear that!Smoky

© 2022 - 2024 — McMap. All rights reserved.