SSH-unix-sockets

Many Linux services (like D-Bus, PostgreSQL, Docker, etc.) are made accessible locally using a UNIX socket. In this article, we'll show how you can access such services remotely from .NET using SSH port forwarding.

UNIX sockets

UNIX domain sockets provide a way to exchange data between processes running on the same host. This approach also brings some security features. First, it isn't possible to access them via the network. Second, we can identify the userid of the other process and use that to authorize the user. And, finally, UNIX domain sockets are identified with a path in the file system. To access a service, the user must have permissions to the path. SELinux allows even more fine-grained control.

To access such services remotely, we could make them accessible using TCP sockets instead of UNIX sockets. However, this makes the service responsible for implementing authentication (identifying users) and encryption (ensuring the messages can't be understood by a third party). Alternatively, we can use SSH port forwarding.

SSH port forwarding

Secure shell (SSH) is a well-known, secure mechanism for running commands on a remote machine. SSH includes a mechanism for authenticating against the remote system, and it provides an encrypted channel for communication.

A (perhaps less known) feature of SSH is its ability to forward ports. Port forwarding means that a remote socket is made available locally. To do that, the ssh client program will open up a local socket and any connection made to that socket will be forwarded over the secure channel and delivered to the socket on the remote machine by the SSH server.

A port forward can be set up by passing the -L flag to the ssh client:

-L [bind_address:]port:host:hostport
-L [bind_address:]port:remote_socket
-L local_socket:host:hostport
-L local_socket:remote_socket

As you can see, we need to specify the local end and the remote end. We can use UNIX sockets (identified by a file system path) or TCP sockets (identified as a host:port).

For example, to make the remote PostgreSQL server running on mydbserver.org available on the local machine at port 1234, we can use the following command:

ssh -L localhost:1234:/var/run/postgresql/.s.PGSQL.5432 mydbserver.org sleep 10

Our -L argument has localhost:1234 for the local TCP end and the path /var/run/postgresql/.s.PGSQL.5432 as the remote UNIX socket end. We are providing the sleep 10 command to make the ssh command exit in case no TCP connections are forwarded in 10 seconds.

The ssh program is not only available on Linux, but it is also part of Windows 10. In the next section, we'll wrap it with a .NET class to provide a cross-platform way to set up a port forward.

Port forwarding from .NET

PortForward.cs provides a simple PortForward class that wraps the ssh client to do port forwarding.

The following example shows how to use it in combination with the Npgsql package to connect to a PostgreSQL server:

using (var portForward = await PortForward.ForwardAsync("tmds@192.168.100.169:/var/run/postgresql/.s.PGSQL.5432"))
{
    var connectionString = $"Server={portForward.IPEndPoint.Address};Port={portForward.IPEndPoint.Port};Database=postgres;User ID=tmds";
    using (var connection = new NpgsqlConnection(connectionString))
    {
        connection.Open();
        Console.WriteLine($"PostgreSQL version: {connection.PostgreSqlVersion}");
    }
}

In this example, we are using the preconfigured private key of the user. You can also explicitly specify a key file using PortForwardOptions.IdentityFile:

var portForward = await PortForward.ForwardAsync(..., o => o.IdentityFile = "mysecretkeyfile");

Conclusion

In this article, you’ve learned how SSH port forwarding allows you to access remote UNIX sockets. We’ve shown how you can set up port forwarding using the ssh client program and use that from a .NET application.

Last updated: February 5, 2024