Search code examples
asp.net-corenginxkestrel-http-serverunix-socket

Set Kestrel Unix socket file permissions in ASP.NET Core?


Kestrel can be configured to use a Unix socket to communicate with a reverse proxy (i.e. nginx) for a slight performance advantage. However, the socket file is deleted and recreated each time the kestrel server stops/starts, resetting the socket's permissions and depending on system configuration, blocking nginx from accessing the socket.

What is a simple and reliable method to ensure Kestrel's Unix socket permissions are opened up on creation?


Solution

  • The following sample Program.Main demonstrates the use of chmod via P/Invoke. This solution allows for use of managed sockets on Windows and switches to libuv when ListenUnixSocket is configured via appsettings.json, and in that case, calls chmod directly on startup to establish socket permissions.

    Code for Chmod class largely lifted from: https://silvercircle.github.io/2018/08/26/serving-net-core-kestrel-linux-unix-sockets/

    UseLibuv requires dependency on Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv via NuGet.

    namespace UnixSocketDemo
    {
        using Microsoft.AspNetCore;
        using Microsoft.AspNetCore.Hosting;
        using Microsoft.Extensions.Configuration;
        using System;
        using System.Diagnostics.CodeAnalysis;
        using System.IO;
        using System.Runtime.InteropServices;
        using System.Threading.Tasks;
        using static System.String;
    
        public class Program
        {
            private static string _unixSocket = null;
    
            public static async Task Main(string[] args)
            {
                var webHost = BuildWebHost(args);
                await webHost.StartAsync();
                if (!IsNullOrWhiteSpace(_unixSocket))
                    Chmod.Set(_unixSocket);
    
                await webHost.WaitForShutdownAsync();
            }
    
            public static IWebHost BuildWebHost(string[] args)
            {
                var builder = WebHost.CreateDefaultBuilder(args)
                    .ConfigureAppConfiguration((hostingContext, config) => config.SetBasePath(Directory.GetCurrentDirectory()))
                    .UseStartup<Startup>();
    
                var config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
                var kestrelConfig = config.GetSection("Kestrel");
                if (kestrelConfig.Exists())
                {
                    _unixSocket = kestrelConfig.GetValue<string>("ListenUnixSocket");
                    if (!IsNullOrWhiteSpace(_unixSocket))
                        builder.UseLibuv();
    
                    builder.ConfigureKestrel((hostingContext, serverOptions) =>
                    {
                        serverOptions.Configure(kestrelConfig);
                        if (!IsNullOrWhiteSpace(_unixSocket))
                            serverOptions.ListenUnixSocket(_unixSocket);
                    });
                }
    
                return builder.Build();
            }
    
            private static class Chmod
            {
                [DllImport("libc", EntryPoint="chmod", SetLastError = true)]
                [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "interop")]
                private static extern int chmod(string pathname, int mode);
    
                // user permissions
                const int S_IRUSR = 0x100;
                const int S_IWUSR = 0x80;
                const int S_IXUSR = 0x40;
    
                // group permission
                const int S_IRGRP = 0x20;
                const int S_IWGRP = 0x10;
                const int S_IXGRP = 0x8;
    
                // other permissions
                const int S_IROTH = 0x4;
                const int S_IWOTH = 0x2;
                const int S_IXOTH = 0x1;
    
                public static void Set(string filename)
                {
                    const int _0755 =
                        S_IRUSR | S_IXUSR | S_IWUSR
                        | S_IRGRP | S_IXGRP | S_IWGRP
                        | S_IROTH | S_IXOTH | S_IWOTH;
    
                    if (0 != chmod(Path.GetFullPath(filename), (int)_0755))
                        throw new Exception("Could not set Unix socket permissions");
                }
            }
        }
    }