Add support for localhost.run
This commit is contained in:
parent
00b362621f
commit
9dc4254647
4 changed files with 259 additions and 99 deletions
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -118,9 +118,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.86"
|
version = "1.0.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
|
@ -295,9 +295,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.1.16"
|
version = "1.1.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b"
|
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
@ -383,9 +383,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.13"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
|
checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -1533,6 +1533,7 @@ dependencies = [
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"russh",
|
"russh",
|
||||||
|
"termsize",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1678,18 +1679,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.209"
|
version = "1.0.210"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
|
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.209"
|
version = "1.0.210"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1698,9 +1699,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.127"
|
version = "1.0.128"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -1913,6 +1914,16 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termsize"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f11ff5c25c172608d5b85e2fb43ee9a6d683a7f4ab7f96ae07b3d8b590368fd"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.63"
|
version = "1.0.63"
|
||||||
|
@ -1974,9 +1985,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.15"
|
version = "0.1.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
|
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -1986,9 +1997,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.11"
|
version = "0.7.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
|
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
|
@ -18,6 +18,7 @@ http = "1.1.0"
|
||||||
hyper = { version = "1", features = ["full"] }
|
hyper = { version = "1", features = ["full"] }
|
||||||
hyper-util = { version = "0.1", features = ["full"] }
|
hyper-util = { version = "0.1", features = ["full"] }
|
||||||
russh = "0.45"
|
russh = "0.45"
|
||||||
|
termsize = "0.1.9"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
tokio-stream = { version = "0.1.15", features = ["net", "sync"] }
|
tokio-stream = { version = "0.1.15", features = ["net", "sync"] }
|
||||||
tokio-util = "0.7.11"
|
tokio-util = "0.7.11"
|
||||||
|
|
16
README.md
16
README.md
|
@ -2,4 +2,18 @@
|
||||||
|
|
||||||
A Rust project demonstrating how to serve Axum's HTTP server on a remote host's port, using SSH tunneling and streaming to avoid opening a socket on the client.
|
A Rust project demonstrating how to serve Axum's HTTP server on a remote host's port, using SSH tunneling and streaming to avoid opening a socket on the client.
|
||||||
|
|
||||||
Tokio, Tower, hyper, and `async` are responsible for gluing everything together. They are pretty awesome! The hardest part to implement was Axum's half; mainly, figuring out how to accept a streaming socket instead of the default TcpListener.
|
Tokio, Tower, and hyper are responsible for gluing everything together with async. They are pretty awesome!
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
With [`localhost.run`](https://localhost.run/):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run -- localhost.run -i ~/.ssh/id_ed25519 -l username --request-pty ""
|
||||||
|
```
|
||||||
|
|
||||||
|
With [`sish`](https://github.com/antoniomika/sish):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run -- tuns.sh -i ~/.ssh/id_ed25519 -R test
|
||||||
|
```
|
||||||
|
|
298
src/main.rs
298
src/main.rs
|
@ -8,7 +8,7 @@ use std::{
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use axum::{extract::State, routing::get, Router};
|
use axum::{extract::State, routing::get, Router};
|
||||||
use clap::Parser;
|
use clap::{Args, Parser};
|
||||||
use hyper::service::service_fn;
|
use hyper::service::service_fn;
|
||||||
use hyper_util::{
|
use hyper_util::{
|
||||||
rt::{TokioExecutor, TokioIo},
|
rt::{TokioExecutor, TokioIo},
|
||||||
|
@ -17,10 +17,14 @@ use hyper_util::{
|
||||||
use russh::{
|
use russh::{
|
||||||
client::{self, Config, Handle, KeyboardInteractiveAuthResponse, Msg, Session},
|
client::{self, Config, Handle, KeyboardInteractiveAuthResponse, Msg, Session},
|
||||||
keys::{decode_secret_key, key},
|
keys::{decode_secret_key, key},
|
||||||
Channel, ChannelMsg, Disconnect,
|
Channel, ChannelId, ChannelMsg, Disconnect,
|
||||||
};
|
};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::{fs, io::stdout, time::sleep};
|
use tokio::{
|
||||||
|
fs,
|
||||||
|
io::{stderr, stdout},
|
||||||
|
time::sleep,
|
||||||
|
};
|
||||||
use tower::Service;
|
use tower::Service;
|
||||||
use tracing::{debug, debug_span, error, info, trace, warn};
|
use tracing::{debug, debug_span, error, info, trace, warn};
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
|
@ -32,24 +36,42 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None)]
|
||||||
struct ClapArgs {
|
struct ClapArgs {
|
||||||
/// SSH hostname
|
/// SSH hostname
|
||||||
#[arg(short = 'H', long)]
|
|
||||||
host: String,
|
host: String,
|
||||||
|
|
||||||
|
/// Identity file containing private key
|
||||||
|
#[arg(short, long, default_value_t = String::from(""))]
|
||||||
|
login_name: String,
|
||||||
|
|
||||||
/// SSH port
|
/// SSH port
|
||||||
#[arg(short, long, default_value_t = 22)]
|
#[arg(short, long, default_value_t = 22)]
|
||||||
port: u16,
|
port: u16,
|
||||||
|
|
||||||
/// Identity file containing private key
|
#[command(flatten)]
|
||||||
#[arg(short, long)]
|
auth: Option<Authentication>,
|
||||||
identity_file: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Remote hostname to bind to
|
/// Remote hostname to bind to
|
||||||
#[arg(short, long, default_value_t = String::from("localhost"))]
|
#[arg(short = 'R', long, default_value_t = String::from(""))]
|
||||||
remote_host: String,
|
remote_host: String,
|
||||||
|
|
||||||
/// Remote port to bind to
|
/// Remote port to bind to
|
||||||
#[arg(short = 't', long, default_value_t = 80)]
|
#[arg(short = 'P', long, default_value_t = 80)]
|
||||||
remote_port: u16,
|
remote_port: u16,
|
||||||
|
|
||||||
|
/// Request a pseudo-terminal to be allocated with the given command.
|
||||||
|
#[arg(long)]
|
||||||
|
request_pty: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
#[group(required = false, multiple = false)]
|
||||||
|
struct Authentication {
|
||||||
|
/// Identity file containing private key.
|
||||||
|
#[arg(short, long, value_name = "FILE")]
|
||||||
|
identity_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Request keyboard-interactive based SSH authentication.
|
||||||
|
#[arg(long)]
|
||||||
|
keyboard_interactive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -60,13 +82,21 @@ async fn main() -> Result<()> {
|
||||||
.init();
|
.init();
|
||||||
trace!("Tracing is up!");
|
trace!("Tracing is up!");
|
||||||
let args = ClapArgs::parse();
|
let args = ClapArgs::parse();
|
||||||
let secret_key = match args.identity_file {
|
let session_auth = match args.auth {
|
||||||
None => None,
|
None => None,
|
||||||
Some(file) => {
|
Some(auth) => {
|
||||||
let secret_key = fs::read_to_string(file)
|
if auth.keyboard_interactive {
|
||||||
.await
|
Some(SessionAuth::KeyboardInteractive)
|
||||||
.with_context(|| "Failed to open secret key")?;
|
} else if let Some(file) = auth.identity_file {
|
||||||
Some(decode_secret_key(&secret_key, None).with_context(|| "Invalid secret key")?)
|
let secret_key = fs::read_to_string(file)
|
||||||
|
.await
|
||||||
|
.with_context(|| "Failed to open secret key")?;
|
||||||
|
Some(SessionAuth::SecretKey(Arc::new(
|
||||||
|
decode_secret_key(&secret_key, None).with_context(|| "Invalid secret key")?,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let config = Arc::new(client::Config {
|
let config = Arc::new(client::Config {
|
||||||
|
@ -75,18 +105,23 @@ async fn main() -> Result<()> {
|
||||||
let mut session = TcpForwardSession::connect(
|
let mut session = TcpForwardSession::connect(
|
||||||
&args.host,
|
&args.host,
|
||||||
args.port,
|
args.port,
|
||||||
|
&args.login_name,
|
||||||
config,
|
config,
|
||||||
secret_key.map(|key| Arc::new(key)),
|
&session_auth,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Initial connection failed")?;
|
.with_context(|| "Initial connection failed")?;
|
||||||
loop {
|
loop {
|
||||||
match session
|
match session
|
||||||
.start_forwarding(&args.remote_host, args.remote_port)
|
.start_forwarding(
|
||||||
|
&args.remote_host,
|
||||||
|
args.remote_port,
|
||||||
|
args.request_pty.as_deref(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Err(e) => error!(error = ?e, "TCP forward session failed."),
|
Err(e) => error!(error = ?e, "TCP forward session failed."),
|
||||||
_ => info!("Connection closed."),
|
Ok(code) => info!("Connection closed with code {}.", code),
|
||||||
}
|
}
|
||||||
debug!("Attempting graceful disconnect.");
|
debug!("Attempting graceful disconnect.");
|
||||||
if let Err(e) = session.close().await {
|
if let Err(e) = session.close().await {
|
||||||
|
@ -98,6 +133,7 @@ async fn main() -> Result<()> {
|
||||||
.reconnect_with(
|
.reconnect_with(
|
||||||
&args.host,
|
&args.host,
|
||||||
args.port,
|
args.port,
|
||||||
|
&args.login_name,
|
||||||
iter::from_fn(move || {
|
iter::from_fn(move || {
|
||||||
reconnect_attempt += 1;
|
reconnect_attempt += 1;
|
||||||
if reconnect_attempt <= 5 {
|
if reconnect_attempt <= 5 {
|
||||||
|
@ -141,10 +177,17 @@ async fn hello(State(state): State<AppState>) -> String {
|
||||||
|
|
||||||
/* Russh session and client */
|
/* Russh session and client */
|
||||||
|
|
||||||
|
/// Private type to decide on the authentication method.
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum SessionAuth {
|
||||||
|
SecretKey(Arc<key::KeyPair>),
|
||||||
|
KeyboardInteractive,
|
||||||
|
}
|
||||||
|
|
||||||
/// User-implemented session type as a helper for interfacing with the SSH protocol.
|
/// User-implemented session type as a helper for interfacing with the SSH protocol.
|
||||||
struct TcpForwardSession {
|
struct TcpForwardSession {
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
secret_key: Option<Arc<key::KeyPair>>,
|
session_auth: Option<SessionAuth>,
|
||||||
session: Handle<Client>,
|
session: Handle<Client>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,8 +197,9 @@ impl TcpForwardSession {
|
||||||
async fn connect(
|
async fn connect(
|
||||||
host: &str,
|
host: &str,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
login_name: &str,
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
secret_key: Option<Arc<key::KeyPair>>,
|
session_auth: &Option<SessionAuth>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let span = debug_span!("TcpForwardSession.connect");
|
let span = debug_span!("TcpForwardSession.connect");
|
||||||
let _enter = span;
|
let _enter = span;
|
||||||
|
@ -164,101 +208,133 @@ impl TcpForwardSession {
|
||||||
let mut session = client::connect(Arc::clone(&config), (host, port), client)
|
let mut session = client::connect(Arc::clone(&config), (host, port), client)
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Unable to connect to remote host.")?;
|
.with_context(|| "Unable to connect to remote host.")?;
|
||||||
let authentication_result = match secret_key.as_ref() {
|
let session = match session_auth {
|
||||||
None => None,
|
Some(SessionAuth::SecretKey(ref secret_key)) => {
|
||||||
Some(secret_key) => {
|
|
||||||
if session
|
if session
|
||||||
.authenticate_publickey("root", Arc::clone(&secret_key))
|
.authenticate_publickey(login_name, Arc::clone(secret_key))
|
||||||
.await
|
.await
|
||||||
.with_context(|| "Error while authenticating with public key.")?
|
.with_context(|| "Error while authenticating with public key.")?
|
||||||
{
|
{
|
||||||
debug!("Public key authentication succeeded!");
|
debug!("Public key authentication succeeded!");
|
||||||
Some(Ok(()))
|
Ok(session)
|
||||||
} else {
|
} else {
|
||||||
Some(Err(anyhow!("Public key authentication failed.")))
|
Err(anyhow!("Public key authentication failed."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(SessionAuth::KeyboardInteractive) => {
|
||||||
|
match session
|
||||||
|
.authenticate_keyboard_interactive_start(login_name, None)
|
||||||
|
.await
|
||||||
|
.with_context(|| {
|
||||||
|
"Error while authenticating with keyboard interactive session."
|
||||||
|
})? {
|
||||||
|
KeyboardInteractiveAuthResponse::Success => {
|
||||||
|
debug!("Keyboard interactive authentication succeeded!");
|
||||||
|
Ok(session)
|
||||||
|
}
|
||||||
|
KeyboardInteractiveAuthResponse::Failure => {
|
||||||
|
Err(anyhow!("Keyboard interactive authentication failed."))
|
||||||
|
}
|
||||||
|
response => Err(anyhow!(
|
||||||
|
"Unhandled keyboard interactive authentication event {:?}",
|
||||||
|
response
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if session
|
||||||
|
.authenticate_none(login_name)
|
||||||
|
.await
|
||||||
|
.with_context(|| "Error while authenticating without credentials.")?
|
||||||
|
{
|
||||||
|
debug!("Authentication without credentials succeeded!");
|
||||||
|
Ok(session)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Authentication without credentials failed."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if matches!(authentication_result, None | Some(Err(_))) {
|
match session {
|
||||||
if authentication_result.is_some() {
|
Ok(session) => Ok(Self {
|
||||||
debug!(
|
config,
|
||||||
"Public key authentication failed; trying keyboard interactive authentication..."
|
session,
|
||||||
);
|
session_auth: session_auth.clone(),
|
||||||
}
|
}),
|
||||||
match session
|
Err(e) => Err(e),
|
||||||
.authenticate_keyboard_interactive_start("russh-axum-tcpip-forward", None)
|
|
||||||
.await
|
|
||||||
.with_context(|| "Error while authenticating with keyboard interactive session.")?
|
|
||||||
{
|
|
||||||
KeyboardInteractiveAuthResponse::Success => {
|
|
||||||
debug!("Keyboard interactive authentication succeeded!");
|
|
||||||
}
|
|
||||||
KeyboardInteractiveAuthResponse::Failure => match authentication_result {
|
|
||||||
None => return Err(anyhow!("Keyboard interactive authentication failed.")),
|
|
||||||
Some(Err(result)) => {
|
|
||||||
debug!("Keyboard interactive authentication failed; propagating public key authentication error...");
|
|
||||||
return Err(result);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
response => match authentication_result {
|
|
||||||
None => {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Unhandled keyboard interactive authentication event {:?}",
|
|
||||||
response
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Some(Err(result)) => {
|
|
||||||
debug!("Keyboard interactive authentication failed; propagating public key authentication error...");
|
|
||||||
return Err(result);
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(Self {
|
|
||||||
config,
|
|
||||||
session,
|
|
||||||
secret_key,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a port forwarding request and opens a session to receive miscellaneous data.
|
/// Sends a port forwarding request and opens a session to receive miscellaneous data.
|
||||||
/// The function yields when the session is broken (for example, if the connection was lost).
|
/// The function yields when the session is broken (for example, if the connection was lost).
|
||||||
async fn start_forwarding(&mut self, remote_host: &str, remote_port: u16) -> Result<u32> {
|
async fn start_forwarding(
|
||||||
|
&mut self,
|
||||||
|
remote_host: &str,
|
||||||
|
remote_port: u16,
|
||||||
|
request_pty: Option<&str>,
|
||||||
|
) -> Result<u32> {
|
||||||
let span = debug_span!("TcpForwardSession.start");
|
let span = debug_span!("TcpForwardSession.start");
|
||||||
let _enter = span;
|
let _enter = span;
|
||||||
self.session
|
self.session
|
||||||
.tcpip_forward(remote_host, remote_port.into())
|
.tcpip_forward(remote_host, remote_port.into())
|
||||||
.await
|
.await
|
||||||
.with_context(|| "tcpip_forward error.")?;
|
.with_context(|| "tcpip_forward error.")?;
|
||||||
|
debug!("Requested tcpip_forward session.");
|
||||||
let mut channel = self
|
let mut channel = self
|
||||||
.session
|
.session
|
||||||
.channel_open_session()
|
.channel_open_session()
|
||||||
.await
|
.await
|
||||||
.with_context(|| "channel_open_session error.")?;
|
.with_context(|| "channel_open_session error.")?;
|
||||||
debug!("Created open session channel.");
|
debug!("Created open session channel.");
|
||||||
|
// let mut stdin = stdin();
|
||||||
let mut stdout = stdout();
|
let mut stdout = stdout();
|
||||||
let mut code = 0;
|
let mut stderr = stderr();
|
||||||
loop {
|
if let Some(cmd) = request_pty {
|
||||||
|
let size = termsize::get().unwrap();
|
||||||
|
channel
|
||||||
|
.request_pty(
|
||||||
|
false,
|
||||||
|
&std::env::var("TERM").unwrap_or("xterm".into()),
|
||||||
|
size.cols.into(),
|
||||||
|
size.rows.into(),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_context(|| "Unable to request pseudo-terminal.")?;
|
||||||
|
debug!("Requested pseudo-terminal.");
|
||||||
|
channel
|
||||||
|
.exec(true, cmd)
|
||||||
|
.await
|
||||||
|
.with_context(|| "Unable to execute command for pseudo-terminal.")?;
|
||||||
|
};
|
||||||
|
let code = loop {
|
||||||
let Some(msg) = channel.wait().await else {
|
let Some(msg) = channel.wait().await else {
|
||||||
return Err(anyhow!("Unexpected end of channel."));
|
return Err(anyhow!("Unexpected end of channel."));
|
||||||
};
|
};
|
||||||
trace!("Got a message!");
|
trace!("Got a message through initial session!");
|
||||||
match msg {
|
match msg {
|
||||||
ChannelMsg::Data { ref data } => {
|
ChannelMsg::Data { ref data } => {
|
||||||
stdout.write_all(data).await?;
|
stdout.write_all(data).await?;
|
||||||
stdout.flush().await?;
|
stdout.flush().await?;
|
||||||
}
|
}
|
||||||
ChannelMsg::Close => break,
|
ChannelMsg::ExtendedData { ref data, ext: 1 } => {
|
||||||
|
stderr.write_all(data).await?;
|
||||||
|
stderr.flush().await?;
|
||||||
|
}
|
||||||
|
ChannelMsg::Success => (),
|
||||||
|
ChannelMsg::Close => break 0,
|
||||||
ChannelMsg::ExitStatus { exit_status } => {
|
ChannelMsg::ExitStatus { exit_status } => {
|
||||||
debug!("Exited with code {exit_status}");
|
debug!("Exited with code {exit_status}");
|
||||||
channel.eof().await?;
|
channel
|
||||||
code = exit_status;
|
.eof()
|
||||||
|
.await
|
||||||
|
.with_context(|| "Unable to close connection.")?;
|
||||||
|
break exit_status;
|
||||||
}
|
}
|
||||||
msg => return Err(anyhow!("Unknown message type {:?}.", msg)),
|
msg => return Err(anyhow!("Unknown message type {:?}.", msg)),
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
Ok(code)
|
Ok(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,12 +347,17 @@ impl TcpForwardSession {
|
||||||
self,
|
self,
|
||||||
host: &str,
|
host: &str,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
login_name: &str,
|
||||||
timer_iterator: impl Iterator<Item = Duration>,
|
timer_iterator: impl Iterator<Item = Duration>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let TcpForwardSession {
|
let TcpForwardSession {
|
||||||
config, secret_key, ..
|
config,
|
||||||
|
session_auth,
|
||||||
|
..
|
||||||
} = self;
|
} = self;
|
||||||
match TcpForwardSession::connect(host, port, config.clone(), secret_key.clone()).await {
|
match TcpForwardSession::connect(host, port, login_name, config.clone(), &session_auth)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let mut e = err;
|
let mut e = err;
|
||||||
for (i, duration) in timer_iterator.enumerate() {
|
for (i, duration) in timer_iterator.enumerate() {
|
||||||
|
@ -285,8 +366,9 @@ impl TcpForwardSession {
|
||||||
e = match TcpForwardSession::connect(
|
e = match TcpForwardSession::connect(
|
||||||
host,
|
host,
|
||||||
port,
|
port,
|
||||||
|
login_name,
|
||||||
config.clone(),
|
config.clone(),
|
||||||
secret_key.clone(),
|
&session_auth,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -323,9 +405,10 @@ impl client::Handler for Client {
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
/// Always accept the SSH server's pubkey. Don't do this in production.
|
/// Always accept the SSH server's pubkey. Don't do this in production.
|
||||||
|
#[allow(unused_variables)]
|
||||||
async fn check_server_key(
|
async fn check_server_key(
|
||||||
&mut self,
|
&mut self,
|
||||||
_server_public_key: &key::PublicKey,
|
server_public_key: &key::PublicKey,
|
||||||
) -> Result<bool, Self::Error> {
|
) -> Result<bool, Self::Error> {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
@ -338,6 +421,7 @@ impl client::Handler for Client {
|
||||||
/// AsyncRead/Write stream into a `hyper` IO object.
|
/// AsyncRead/Write stream into a `hyper` IO object.
|
||||||
///
|
///
|
||||||
/// See also: [axum/examples/serve-with-hyper](https://github.com/tokio-rs/axum/blob/main/examples/serve-with-hyper/src/main.rs)
|
/// See also: [axum/examples/serve-with-hyper](https://github.com/tokio-rs/axum/blob/main/examples/serve-with-hyper/src/main.rs)
|
||||||
|
#[allow(unused_variables)]
|
||||||
async fn server_channel_open_forwarded_tcpip(
|
async fn server_channel_open_forwarded_tcpip(
|
||||||
&mut self,
|
&mut self,
|
||||||
channel: Channel<Msg>,
|
channel: Channel<Msg>,
|
||||||
|
@ -347,7 +431,7 @@ impl client::Handler for Client {
|
||||||
originator_port: u32,
|
originator_port: u32,
|
||||||
session: &mut Session,
|
session: &mut Session,
|
||||||
) -> Result<(), Self::Error> {
|
) -> Result<(), Self::Error> {
|
||||||
let span = debug_span!("server_channel_open_forwarded_tcpip",);
|
let span = debug_span!("server_channel_open_forwarded_tcpip");
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
debug!(
|
debug!(
|
||||||
sshid = %String::from_utf8_lossy(session.remote_sshid()),
|
sshid = %String::from_utf8_lossy(session.remote_sshid()),
|
||||||
|
@ -357,7 +441,6 @@ impl client::Handler for Client {
|
||||||
originator_port = originator_port,
|
originator_port = originator_port,
|
||||||
"New connection!"
|
"New connection!"
|
||||||
);
|
);
|
||||||
// Get our router from the lazy static.
|
|
||||||
let router = &*ROUTER;
|
let router = &*ROUTER;
|
||||||
let service = service_fn(move |req| router.clone().call(req));
|
let service = service_fn(move |req| router.clone().call(req));
|
||||||
let server = Builder::new(TokioExecutor::new());
|
let server = Builder::new(TokioExecutor::new());
|
||||||
|
@ -366,8 +449,59 @@ impl client::Handler for Client {
|
||||||
server
|
server
|
||||||
.serve_connection_with_upgrades(TokioIo::new(channel.into_stream()), service)
|
.serve_connection_with_upgrades(TokioIo::new(channel.into_stream()), service)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.expect("Invalid request");
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
async fn auth_banner(
|
||||||
|
&mut self,
|
||||||
|
banner: &str,
|
||||||
|
session: &mut Session,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
debug!("Received auth banner.");
|
||||||
|
let mut stdout = stdout();
|
||||||
|
stdout.write_all(banner.as_bytes()).await?;
|
||||||
|
stdout.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
async fn exit_status(
|
||||||
|
&mut self,
|
||||||
|
channel: ChannelId,
|
||||||
|
exit_status: u32,
|
||||||
|
session: &mut Session,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
debug!(channel = ?channel, "exit_status");
|
||||||
|
if exit_status == 0 {
|
||||||
|
info!("Remote exited with status {}.", exit_status);
|
||||||
|
} else {
|
||||||
|
info!("Remote exited with status {}.", exit_status);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
async fn channel_open_confirmation(
|
||||||
|
&mut self,
|
||||||
|
channel: ChannelId,
|
||||||
|
max_packet_size: u32,
|
||||||
|
window_size: u32,
|
||||||
|
session: &mut Session,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
debug!(channel = ?channel, max_packet_size, window_size, "channel_open_confirmation");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
async fn channel_success(
|
||||||
|
&mut self,
|
||||||
|
channel: ChannelId,
|
||||||
|
session: &mut Session,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
debug!(channel = ?channel, "channel_success");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue