media: let synthetic probe use mTLS
This commit is contained in:
parent
e2c86ea4c4
commit
3fd1c349a9
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_client"
|
||||
version = "0.22.49"
|
||||
version = "0.22.50"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1686,7 +1686,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_common"
|
||||
version = "0.22.49"
|
||||
version = "0.22.50"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@ -1698,7 +1698,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_server"
|
||||
version = "0.22.49"
|
||||
version = "0.22.50"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.22.49"
|
||||
version = "0.22.50"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.22.49"
|
||||
version = "0.22.50"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -254,6 +254,7 @@ from `LESAVKA_CLIENT_PKI_SSH_SOURCE` over SSH. Runtime clients require the insta
|
||||
| `LESAVKA_TLS_DOMAIN` | client transport SNI/domain override when dialing by IP |
|
||||
| `LESAVKA_TLS_KEY` | server TLS private-key path override |
|
||||
| `LESAVKA_TLS_SAN` | server installer extra certificate SAN list for additional relay hostnames/IPs |
|
||||
| `LESAVKA_SYNTHETIC_PKI_DIR` | server installer destination for the SSH user's local synthetic-probe mTLS identity; defaults to `~/.config/lesavka/pki` |
|
||||
| `LESAVKA_UAC_BUFFER_TIME_US` | server audio sink latency override |
|
||||
| `LESAVKA_UAC_COMPENSATION_US` | server audio sink latency override |
|
||||
| `LESAVKA_UAC_DEV` | server hardware/device override |
|
||||
|
||||
@ -84,6 +84,7 @@ INSTALL_UVC_FRAME_META_LOG_PATH=${LESAVKA_INSTALL_UVC_FRAME_META_LOG_PATH:-${LES
|
||||
INSTALL_SERVER_BIND_ADDR=${LESAVKA_INSTALL_SERVER_BIND_ADDR:-0.0.0.0:50051}
|
||||
LESAVKA_TLS_DIR=${LESAVKA_TLS_DIR:-/etc/lesavka/pki}
|
||||
LESAVKA_CLIENT_BUNDLE=${LESAVKA_CLIENT_BUNDLE:-/etc/lesavka/lesavka-client-pki.tar.gz}
|
||||
LESAVKA_SYNTHETIC_PKI_DIR=${LESAVKA_SYNTHETIC_PKI_DIR:-$USER_HOME/.config/lesavka/pki}
|
||||
LESAVKA_PASTE_KEY_FILE=${LESAVKA_PASTE_KEY_FILE:-/etc/lesavka/paste-key}
|
||||
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
|
||||
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=135090
|
||||
@ -505,6 +506,13 @@ ensure_server_tls_pki() {
|
||||
sudo tar -C "$bundle_tmp" -czf "$LESAVKA_CLIENT_BUNDLE" ca.crt client.crt client.key paste-key
|
||||
sudo chown "$ORIG_USER":"$ORIG_USER" "$LESAVKA_CLIENT_BUNDLE"
|
||||
sudo chmod 0600 "$LESAVKA_CLIENT_BUNDLE"
|
||||
if [[ -n $USER_HOME && $ORIG_USER != root ]]; then
|
||||
sudo install -d -m 0700 -o "$ORIG_USER" -g "$ORIG_USER" "$LESAVKA_SYNTHETIC_PKI_DIR"
|
||||
sudo install -m 0644 -o "$ORIG_USER" -g "$ORIG_USER" "$LESAVKA_TLS_DIR/ca.crt" "$LESAVKA_SYNTHETIC_PKI_DIR/ca.crt"
|
||||
sudo install -m 0644 -o "$ORIG_USER" -g "$ORIG_USER" "$LESAVKA_TLS_DIR/client.crt" "$LESAVKA_SYNTHETIC_PKI_DIR/client.crt"
|
||||
sudo install -m 0600 -o "$ORIG_USER" -g "$ORIG_USER" "$LESAVKA_TLS_DIR/client.key" "$LESAVKA_SYNTHETIC_PKI_DIR/client.key"
|
||||
echo " ↪ local synthetic probe TLS identity: $LESAVKA_SYNTHETIC_PKI_DIR"
|
||||
fi
|
||||
sudo rm -rf "$bundle_tmp"
|
||||
echo " ↪ client enrollment bundle: $LESAVKA_CLIENT_BUNDLE"
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ def parse_args() -> argparse.Namespace:
|
||||
)
|
||||
parser.add_argument("--inject-host", default="", help="Theia SSH host, e.g. titan-jh")
|
||||
parser.add_argument("--rct-host", default="", help="RCT SSH host, e.g. tethys")
|
||||
parser.add_argument("--server", default="http://127.0.0.1:50051")
|
||||
parser.add_argument("--server", default="https://127.0.0.1:50051")
|
||||
parser.add_argument("--inject-binary", default="/usr/local/bin/lesavka-synthetic-uplink")
|
||||
parser.add_argument("--mode", default="1280x720@30", help=f"one mode; baseline set is {DEFAULT_MODES}")
|
||||
parser.add_argument("--width", type=int, default=0, help="override capture width")
|
||||
|
||||
@ -16,7 +16,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.22.49"
|
||||
version = "0.22.50"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ use lesavka_common::lesavka::{
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tonic::Request;
|
||||
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
|
||||
|
||||
const DEFAULT_SERVER: &str = "http://127.0.0.1:50051";
|
||||
const DEFAULT_SAMPLE_RATE: u32 = 48_000;
|
||||
@ -31,6 +32,10 @@ struct Args {
|
||||
session_id: u64,
|
||||
artifact_dir: Option<PathBuf>,
|
||||
print_every: u64,
|
||||
tls_ca: Option<PathBuf>,
|
||||
tls_client_cert: Option<PathBuf>,
|
||||
tls_client_key: Option<PathBuf>,
|
||||
tls_domain: Option<String>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
@ -45,6 +50,14 @@ impl Args {
|
||||
session_id: unix_millis(),
|
||||
artifact_dir: None,
|
||||
print_every: 150,
|
||||
tls_ca: env_path("LESAVKA_TLS_CA").or_else(|| default_pki_path("ca.crt")),
|
||||
tls_client_cert: env_path("LESAVKA_TLS_CLIENT_CERT")
|
||||
.or_else(|| default_pki_path("client.crt")),
|
||||
tls_client_key: env_path("LESAVKA_TLS_CLIENT_KEY")
|
||||
.or_else(|| default_pki_path("client.key")),
|
||||
tls_domain: std::env::var("LESAVKA_TLS_DOMAIN")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty()),
|
||||
};
|
||||
let mut it = std::env::args().skip(1);
|
||||
while let Some(flag) = it.next() {
|
||||
@ -63,6 +76,14 @@ impl Args {
|
||||
args.artifact_dir = Some(PathBuf::from(next_value(&mut it, &flag)?))
|
||||
}
|
||||
"--print-every" => args.print_every = parse_next(&mut it, &flag)?,
|
||||
"--tls-ca" => args.tls_ca = Some(PathBuf::from(next_value(&mut it, &flag)?)),
|
||||
"--tls-client-cert" => {
|
||||
args.tls_client_cert = Some(PathBuf::from(next_value(&mut it, &flag)?))
|
||||
}
|
||||
"--tls-client-key" => {
|
||||
args.tls_client_key = Some(PathBuf::from(next_value(&mut it, &flag)?))
|
||||
}
|
||||
"--tls-domain" => args.tls_domain = Some(next_value(&mut it, &flag)?),
|
||||
"--mode" => {
|
||||
let value = next_value(&mut it, &flag)?;
|
||||
let (width, height, fps) = parse_mode(&value)?;
|
||||
@ -207,6 +228,85 @@ impl Drop for MjpegEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_channel(args: &Args) -> Result<Channel> {
|
||||
let mut endpoint =
|
||||
tonic::transport::Channel::from_shared(args.server.clone())?.tcp_nodelay(true);
|
||||
if is_https(&args.server) {
|
||||
endpoint = endpoint
|
||||
.tls_config(client_tls_config(args)?)
|
||||
.context("configuring synthetic uplink TLS")?;
|
||||
}
|
||||
endpoint
|
||||
.connect()
|
||||
.await
|
||||
.with_context(|| format!("connecting to {}", args.server))
|
||||
}
|
||||
|
||||
fn client_tls_config(args: &Args) -> Result<ClientTlsConfig> {
|
||||
let mut tls = ClientTlsConfig::new().domain_name(
|
||||
args.tls_domain
|
||||
.clone()
|
||||
.or_else(|| host_from_uri(&args.server))
|
||||
.unwrap_or_else(|| "lesavka-server".to_string()),
|
||||
);
|
||||
let ca_path = args
|
||||
.tls_ca
|
||||
.as_ref()
|
||||
.context("https synthetic uplink requires --tls-ca or LESAVKA_TLS_CA")?;
|
||||
let cert_path = args
|
||||
.tls_client_cert
|
||||
.as_ref()
|
||||
.context("https synthetic uplink requires --tls-client-cert or LESAVKA_TLS_CLIENT_CERT")?;
|
||||
let key_path = args
|
||||
.tls_client_key
|
||||
.as_ref()
|
||||
.context("https synthetic uplink requires --tls-client-key or LESAVKA_TLS_CLIENT_KEY")?;
|
||||
let ca = std::fs::read(ca_path)
|
||||
.with_context(|| format!("reading TLS CA certificate {}", ca_path.display()))?;
|
||||
tls = tls.ca_certificate(Certificate::from_pem(ca));
|
||||
let cert = std::fs::read(cert_path)
|
||||
.with_context(|| format!("reading TLS client certificate {}", cert_path.display()))?;
|
||||
let key = std::fs::read(key_path)
|
||||
.with_context(|| format!("reading TLS client key {}", key_path.display()))?;
|
||||
Ok(tls.identity(Identity::from_pem(cert, key)))
|
||||
}
|
||||
|
||||
fn is_https(server: &str) -> bool {
|
||||
server.trim_start().starts_with("https://")
|
||||
}
|
||||
|
||||
fn host_from_uri(server: &str) -> Option<String> {
|
||||
let rest = server.split_once("://")?.1;
|
||||
let host_port = rest.split('/').next().unwrap_or(rest);
|
||||
let host = host_port
|
||||
.rsplit_once('@')
|
||||
.map(|(_, host)| host)
|
||||
.unwrap_or(host_port);
|
||||
if host.starts_with('[') {
|
||||
return host
|
||||
.split_once(']')
|
||||
.map(|(value, _)| value.trim_start_matches('[').to_string());
|
||||
}
|
||||
Some(host.split(':').next().unwrap_or(host).to_string()).filter(|host| !host.is_empty())
|
||||
}
|
||||
|
||||
fn env_path(name: &str) -> Option<PathBuf> {
|
||||
std::env::var_os(name)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
|
||||
fn default_pki_path(file_name: &str) -> Option<PathBuf> {
|
||||
let home = std::env::var_os("HOME")?;
|
||||
Some(
|
||||
PathBuf::from(home)
|
||||
.join(".config")
|
||||
.join("lesavka")
|
||||
.join("pki")
|
||||
.join(file_name),
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
let args = Args::parse()?;
|
||||
@ -219,11 +319,7 @@ async fn main() -> Result<()> {
|
||||
std::fs::write(dir.join("summary.json"), args_summary_json(&args) + "\n")?;
|
||||
}
|
||||
|
||||
let channel = tonic::transport::Channel::from_shared(args.server.clone())?
|
||||
.tcp_nodelay(true)
|
||||
.connect()
|
||||
.await
|
||||
.with_context(|| format!("connecting to {}", args.server))?;
|
||||
let channel = connect_channel(&args).await?;
|
||||
let mut client = RelayClient::new(channel);
|
||||
let (tx, rx) = mpsc::channel::<UpstreamMediaBundle>(8);
|
||||
let response_task = tokio::spawn(async move {
|
||||
@ -484,13 +580,14 @@ fn unix_millis() -> u64 {
|
||||
|
||||
fn args_summary_json(args: &Args) -> String {
|
||||
format!(
|
||||
"{{\"schema\":\"lesavka.synthetic-uplink.v1\",\"server\":{server:?},\"width\":{width},\"height\":{height},\"fps\":{fps},\"duration_s\":{duration:.3},\"session_id\":{session}}}",
|
||||
"{{\"schema\":\"lesavka.synthetic-uplink.v1\",\"server\":{server:?},\"width\":{width},\"height\":{height},\"fps\":{fps},\"duration_s\":{duration:.3},\"session_id\":{session},\"tls\":{tls}}}",
|
||||
server = args.server,
|
||||
width = args.width,
|
||||
height = args.height,
|
||||
fps = args.fps,
|
||||
duration = args.duration.as_secs_f64(),
|
||||
session = args.session_id,
|
||||
tls = is_https(&args.server),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,7 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
|
||||
"LESAVKA_TLS_CERT=%s",
|
||||
"LESAVKA_TLS_KEY=%s",
|
||||
"LESAVKA_TLS_CLIENT_CA=%s",
|
||||
"LESAVKA_SYNTHETIC_PKI_DIR",
|
||||
"LESAVKA_PASTE_KEY_FILE=%s",
|
||||
] {
|
||||
assert!(
|
||||
@ -708,6 +709,8 @@ fn server_install_generates_mtls_identity_and_client_bundle() {
|
||||
"Client TLS bundle:",
|
||||
"Client install can use:",
|
||||
"sudo chown \"$ORIG_USER\":\"$ORIG_USER\" \"$LESAVKA_CLIENT_BUNDLE\"",
|
||||
"local synthetic probe TLS identity",
|
||||
"$USER_HOME/.config/lesavka/pki",
|
||||
"LESAVKA_REQUIRE_TLS=%s",
|
||||
"LESAVKA_TLS_CLIENT_CA=%s",
|
||||
] {
|
||||
|
||||
@ -33,6 +33,7 @@ fn synthetic_probe_keeps_bundled_network_ingress_and_rct_comparison_markers() {
|
||||
"lesavka.synthetic-rct-probe.orchestrator.v1",
|
||||
"StreamWebcamMedia",
|
||||
"lesavka-synthetic-uplink",
|
||||
"https://127.0.0.1:50051",
|
||||
"--inject-host",
|
||||
"--rct-host",
|
||||
"--capture-only",
|
||||
@ -80,6 +81,10 @@ fn synthetic_injector_enters_the_public_bundled_media_rpc() {
|
||||
"jpegenc",
|
||||
"client_capture_pts_us: pts_us",
|
||||
"client_send_pts_us: pts_us",
|
||||
"ClientTlsConfig",
|
||||
"--tls-ca",
|
||||
"--tls-client-cert",
|
||||
"--tls-client-key",
|
||||
"StreamWebcamMedia closed before accepting synthetic frame",
|
||||
] {
|
||||
assert!(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user