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]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.22.49"
|
version = "0.22.50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1686,7 +1686,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.22.49"
|
version = "0.22.50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1698,7 +1698,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.22.49"
|
version = "0.22.50"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.22.49"
|
version = "0.22.50"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.22.49"
|
version = "0.22.50"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
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_DOMAIN` | client transport SNI/domain override when dialing by IP |
|
||||||
| `LESAVKA_TLS_KEY` | server TLS private-key path override |
|
| `LESAVKA_TLS_KEY` | server TLS private-key path override |
|
||||||
| `LESAVKA_TLS_SAN` | server installer extra certificate SAN list for additional relay hostnames/IPs |
|
| `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_BUFFER_TIME_US` | server audio sink latency override |
|
||||||
| `LESAVKA_UAC_COMPENSATION_US` | server audio sink latency override |
|
| `LESAVKA_UAC_COMPENSATION_US` | server audio sink latency override |
|
||||||
| `LESAVKA_UAC_DEV` | server hardware/device 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}
|
INSTALL_SERVER_BIND_ADDR=${LESAVKA_INSTALL_SERVER_BIND_ADDR:-0.0.0.0:50051}
|
||||||
LESAVKA_TLS_DIR=${LESAVKA_TLS_DIR:-/etc/lesavka/pki}
|
LESAVKA_TLS_DIR=${LESAVKA_TLS_DIR:-/etc/lesavka/pki}
|
||||||
LESAVKA_CLIENT_BUNDLE=${LESAVKA_CLIENT_BUNDLE:-/etc/lesavka/lesavka-client-pki.tar.gz}
|
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}
|
LESAVKA_PASTE_KEY_FILE=${LESAVKA_PASTE_KEY_FILE:-/etc/lesavka/paste-key}
|
||||||
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
|
DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0
|
||||||
DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=135090
|
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 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 chown "$ORIG_USER":"$ORIG_USER" "$LESAVKA_CLIENT_BUNDLE"
|
||||||
sudo chmod 0600 "$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"
|
sudo rm -rf "$bundle_tmp"
|
||||||
echo " ↪ client enrollment bundle: $LESAVKA_CLIENT_BUNDLE"
|
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("--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("--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("--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("--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")
|
parser.add_argument("--width", type=int, default=0, help="override capture width")
|
||||||
|
|||||||
@ -16,7 +16,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.22.49"
|
version = "0.22.50"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ use lesavka_common::lesavka::{
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tonic::Request;
|
use tonic::Request;
|
||||||
|
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
|
||||||
|
|
||||||
const DEFAULT_SERVER: &str = "http://127.0.0.1:50051";
|
const DEFAULT_SERVER: &str = "http://127.0.0.1:50051";
|
||||||
const DEFAULT_SAMPLE_RATE: u32 = 48_000;
|
const DEFAULT_SAMPLE_RATE: u32 = 48_000;
|
||||||
@ -31,6 +32,10 @@ struct Args {
|
|||||||
session_id: u64,
|
session_id: u64,
|
||||||
artifact_dir: Option<PathBuf>,
|
artifact_dir: Option<PathBuf>,
|
||||||
print_every: u64,
|
print_every: u64,
|
||||||
|
tls_ca: Option<PathBuf>,
|
||||||
|
tls_client_cert: Option<PathBuf>,
|
||||||
|
tls_client_key: Option<PathBuf>,
|
||||||
|
tls_domain: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
@ -45,6 +50,14 @@ impl Args {
|
|||||||
session_id: unix_millis(),
|
session_id: unix_millis(),
|
||||||
artifact_dir: None,
|
artifact_dir: None,
|
||||||
print_every: 150,
|
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);
|
let mut it = std::env::args().skip(1);
|
||||||
while let Some(flag) = it.next() {
|
while let Some(flag) = it.next() {
|
||||||
@ -63,6 +76,14 @@ impl Args {
|
|||||||
args.artifact_dir = Some(PathBuf::from(next_value(&mut it, &flag)?))
|
args.artifact_dir = Some(PathBuf::from(next_value(&mut it, &flag)?))
|
||||||
}
|
}
|
||||||
"--print-every" => args.print_every = parse_next(&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" => {
|
"--mode" => {
|
||||||
let value = next_value(&mut it, &flag)?;
|
let value = next_value(&mut it, &flag)?;
|
||||||
let (width, height, fps) = parse_mode(&value)?;
|
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")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let args = Args::parse()?;
|
let args = Args::parse()?;
|
||||||
@ -219,11 +319,7 @@ async fn main() -> Result<()> {
|
|||||||
std::fs::write(dir.join("summary.json"), args_summary_json(&args) + "\n")?;
|
std::fs::write(dir.join("summary.json"), args_summary_json(&args) + "\n")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let channel = tonic::transport::Channel::from_shared(args.server.clone())?
|
let channel = connect_channel(&args).await?;
|
||||||
.tcp_nodelay(true)
|
|
||||||
.connect()
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("connecting to {}", args.server))?;
|
|
||||||
let mut client = RelayClient::new(channel);
|
let mut client = RelayClient::new(channel);
|
||||||
let (tx, rx) = mpsc::channel::<UpstreamMediaBundle>(8);
|
let (tx, rx) = mpsc::channel::<UpstreamMediaBundle>(8);
|
||||||
let response_task = tokio::spawn(async move {
|
let response_task = tokio::spawn(async move {
|
||||||
@ -484,13 +580,14 @@ fn unix_millis() -> u64 {
|
|||||||
|
|
||||||
fn args_summary_json(args: &Args) -> String {
|
fn args_summary_json(args: &Args) -> String {
|
||||||
format!(
|
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,
|
server = args.server,
|
||||||
width = args.width,
|
width = args.width,
|
||||||
height = args.height,
|
height = args.height,
|
||||||
fps = args.fps,
|
fps = args.fps,
|
||||||
duration = args.duration.as_secs_f64(),
|
duration = args.duration.as_secs_f64(),
|
||||||
session = args.session_id,
|
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_CERT=%s",
|
||||||
"LESAVKA_TLS_KEY=%s",
|
"LESAVKA_TLS_KEY=%s",
|
||||||
"LESAVKA_TLS_CLIENT_CA=%s",
|
"LESAVKA_TLS_CLIENT_CA=%s",
|
||||||
|
"LESAVKA_SYNTHETIC_PKI_DIR",
|
||||||
"LESAVKA_PASTE_KEY_FILE=%s",
|
"LESAVKA_PASTE_KEY_FILE=%s",
|
||||||
] {
|
] {
|
||||||
assert!(
|
assert!(
|
||||||
@ -708,6 +709,8 @@ fn server_install_generates_mtls_identity_and_client_bundle() {
|
|||||||
"Client TLS bundle:",
|
"Client TLS bundle:",
|
||||||
"Client install can use:",
|
"Client install can use:",
|
||||||
"sudo chown \"$ORIG_USER\":\"$ORIG_USER\" \"$LESAVKA_CLIENT_BUNDLE\"",
|
"sudo chown \"$ORIG_USER\":\"$ORIG_USER\" \"$LESAVKA_CLIENT_BUNDLE\"",
|
||||||
|
"local synthetic probe TLS identity",
|
||||||
|
"$USER_HOME/.config/lesavka/pki",
|
||||||
"LESAVKA_REQUIRE_TLS=%s",
|
"LESAVKA_REQUIRE_TLS=%s",
|
||||||
"LESAVKA_TLS_CLIENT_CA=%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",
|
"lesavka.synthetic-rct-probe.orchestrator.v1",
|
||||||
"StreamWebcamMedia",
|
"StreamWebcamMedia",
|
||||||
"lesavka-synthetic-uplink",
|
"lesavka-synthetic-uplink",
|
||||||
|
"https://127.0.0.1:50051",
|
||||||
"--inject-host",
|
"--inject-host",
|
||||||
"--rct-host",
|
"--rct-host",
|
||||||
"--capture-only",
|
"--capture-only",
|
||||||
@ -80,6 +81,10 @@ fn synthetic_injector_enters_the_public_bundled_media_rpc() {
|
|||||||
"jpegenc",
|
"jpegenc",
|
||||||
"client_capture_pts_us: pts_us",
|
"client_capture_pts_us: pts_us",
|
||||||
"client_send_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",
|
"StreamWebcamMedia closed before accepting synthetic frame",
|
||||||
] {
|
] {
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user