Add CreateNamedPipeServerStream to named pipes options #56568
Description
Background and Motivation
Addresses #53306
Named pipes transport allows pipe security to be configured, but it's applied to all of server's named pipe endpoints. An API is needed to customize endpoint security when they're created.
Proposed API
Adds CreateNamedPipeServerStream
func to options, allowing advanced users to customize creating endpoints. They can choose to specify different PipeSecurity
options on a per-endpoint basis.
This feature is modeled after SocketTransportOptions.CreateBoundListenSocket
.
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes;
+ public sealed class CreateNamedPipeServerStreamContext
+ {
+ public required NamedPipeEndPoint NamedPipeEndPoint { get; init; }
+ public PipeOptions PipeOptions { get; init; }
+ public PipeSecurity? PipeSecurity { get; init; }
+ }
public sealed class NamedPipeTransportOptions
{
+ public Func<CreateNamedPipeServerStreamContext, NamedPipeServerStream> CreateNamedPipeServerStream { get; set; }
+ public static NamedPipeServerStream CreateDefaultNamedPipeServerStream(CreateNamedPipeServerStreamContext context);
}
CreateNamedPipeServerStreamContext
is added because more information than just the endpoint is needed when creating a NamedPipeServerStream
. An object provides flexibility for more data to be provided, if needed. Allocations aren't a concern because the context is only created when a stream is created, and streams are cached and reused.
Usage Examples
var builder = new HostBuilder()
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder
.UseKestrel(o =>
{
o.ListenNamedPipe(defaultSecurityPipeName, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1;
});
o.ListenNamedPipe(customSecurityPipeName, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1;
});
})
.UseNamedPipes(options =>
{
var defaultSecurity = new PipeSecurity();
defaultSecurity.AddAccessRule(new PipeAccessRule("Users", PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance, AccessControlType.Allow));
options.PipeSecurity = defaultSecurity;
options.CurrentUserOnly = false;
options.CreateNamedPipeServerStream = (context) =>
{
if (context.NamedPipeEndPoint.PipeName == defaultSecurityPipeName)
{
return NamedPipeTransportOptions.CreateDefaultNamedPipeServerStream(context);
}
var allowSecurity = new PipeSecurity();
allowSecurity.AddAccessRule(new PipeAccessRule("Users", PipeAccessRights.FullControl, AccessControlType.Allow));
return NamedPipeServerStreamAcl.Create(
context.NamedPipeEndPoint.PipeName,
PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte,
context.PipeOptions,
inBufferSize: 0, // Buffer in System.IO.Pipelines
outBufferSize: 0, // Buffer in System.IO.Pipelines
allowSecurity);
};
})
.Configure(app =>
{
app.Run(async context =>
{
await context.Response.WriteAsync("hello, world");
});
});
});