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?
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");
}
}
}
}