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.