Skip to content

Commit

Permalink
SSH agent forwarding
Browse files Browse the repository at this point in the history
  • Loading branch information
nbdd0121 committed Jul 13, 2021
1 parent 4718b82 commit 629efbf
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ interval = "1hr"
iptables_cmd = "sudo iptables-legacy"
# Ports to forward
ports = [ 1234 ]

# Leave out this section to disable SSH agent forwarding
[ssh_agent]
# Default to the path below, can be omitted if unchanged
# Set `SSH_AUTH_SOCK` to the path you specified.
ssh_auth_sock = "/tmp/.wsld/ssh_auth_sock"
```
then run `wsld` and set `DISPLAY=:0`.

Expand Down
14 changes: 14 additions & 0 deletions client/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub struct Config {

#[serde(default)]
pub tcp_forward: Option<TcpForwardConfig>,

#[serde(default)]
pub ssh_agent: Option<SshAgentConfig>,
}

impl Default for Config {
Expand All @@ -27,6 +30,7 @@ impl Default for Config {
time: None,
x11: None,
tcp_forward: None,
ssh_agent: None,
}
}
}
Expand Down Expand Up @@ -85,3 +89,13 @@ pub struct TcpForwardConfig {

pub ports: Vec<u16>,
}

fn default_ssh_auth_sock() -> String {
"/tmp/.wsld/ssh_auth_sock".to_owned()
}

#[derive(Serialize, Deserialize, Debug)]
pub struct SshAgentConfig {
#[serde(default = "default_ssh_auth_sock")]
pub ssh_auth_sock: String,
}
9 changes: 9 additions & 0 deletions client/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod config;
mod ssh_agent;
mod tcp;
mod time;
mod util;
Expand Down Expand Up @@ -101,6 +102,14 @@ async fn main() {
}));
}

if let Some(config) = &CONFIG.ssh_agent {
tasks.push(tokio::task::spawn(async move {
if let Err(err) = ssh_agent::ssh_agent_forward(config).await {
eprintln!("SSH-Agent forwarder error: {}", err);
}
}));
}

for task in tasks {
let _ = task.await;
}
Expand Down
40 changes: 40 additions & 0 deletions client/src/ssh_agent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use super::config::SshAgentConfig;
use super::util::{connect_stream, either};
use super::vmsocket::VmSocket;
use super::CONFIG;

use std::fs::Permissions;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use tokio::io::AsyncWriteExt;
use tokio::net::{UnixListener, UnixStream};

async fn handle_stream(mut stream: UnixStream) -> std::io::Result<()> {
let mut server = VmSocket::connect(CONFIG.service_port).await?;
server.write_all(b"ssha").await?;

let (client_r, client_w) = stream.split();
let (server_r, server_w) = server.split();
let a = connect_stream(client_r, server_w);
let b = connect_stream(server_r, client_w);
either(a, b).await
}

pub async fn ssh_agent_forward(config: &'static SshAgentConfig) -> std::io::Result<()> {
// Remove existing socket
let _ = std::fs::create_dir_all(Path::new(&config.ssh_auth_sock).parent().unwrap());
let _ = std::fs::remove_file(&config.ssh_auth_sock);

let listener = UnixListener::bind(&config.ssh_auth_sock)?;
let _ = std::fs::set_permissions(&config.ssh_auth_sock, Permissions::from_mode(0o600));

loop {
let stream = listener.accept().await?.0;

tokio::task::spawn(async move {
if let Err(err) = handle_stream(stream).await {
eprintln!("Failed to transfer: {}", err);
}
});
}
}
4 changes: 4 additions & 0 deletions docs/impl.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ Windows could access service listening to localhost in WSL2, but WSL2 couldn't d
You might think we can just listen on a TCP port in WSL2 and forward it to Windows through Vsock, just like how we forward X11. However, this is not true. If you listen to a port in WSL2, Windows-to-WSL2 localhost forwarding will kick in, forwarding the `wsldhost` to Windows connection back into `wsld` inside WSL2. This creates a loop and soon both daemons will run out of file descriptors or memory.

We creatively use iptables redirection to achieve this forwarding. `wsld` will only listen on a service port, which is not any of the ports being forwarded. WSL uses `/proc/net/tcp` to determine if a port is being listened on and whether forwarding from Windows to WSL2 should kick in. Because none of the forwarded port is being listened on, we avoid the loop issue. To allow `wsld` to intercept requests sent to forwarded ports, we employ iptables's nat table's OUTPUT chain. `wsld` will add one `REDIRECT` rule for each port being forwarded.

# SSH Agent Forwarding

`wsld` will listen on `/tmp/.wsld/ssh_auth_sock` (or another path configured) and forward the connection to `wsldhost`, which will in turn forward the connection to the named pipe `\\.\pipe\openssh-ssh-agent` which OpenSSH on Windows listens on.
2 changes: 2 additions & 0 deletions server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![windows_subsystem = "windows"]

mod config;
mod ssh_agent;
mod tcp;
mod time;
mod util;
Expand Down Expand Up @@ -33,6 +34,7 @@ async fn handle_stream(mut stream: TcpStream) -> std::io::Result<()> {
b"x11\0" => x11::handle_x11(stream).await,
b"time" => time::handle_time(stream).await,
b"tcp\0" => tcp::handle_tcp(stream).await,
b"ssha" => ssh_agent::handle_ssh_agent(stream).await,
b"noop" => Ok(()),
_ => Err(Error::new(
ErrorKind::InvalidData,
Expand Down
14 changes: 14 additions & 0 deletions server/src/ssh_agent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use super::util::{connect_stream, either};

use tokio::net::windows::named_pipe;
use tokio::net::TcpStream;

pub async fn handle_ssh_agent(mut stream: TcpStream) -> std::io::Result<()> {
let (client_r, client_w) = stream.split();

let server = named_pipe::ClientOptions::new().open(r"\\.\pipe\openssh-ssh-agent")?;
let (server_r, server_w) = tokio::io::split(server);
let a = connect_stream(client_r, server_w);
let b = connect_stream(server_r, client_w);
either(a, b).await
}

0 comments on commit 629efbf

Please sign in to comment.