diff --git a/Cargo.lock b/Cargo.lock index 0c1c713a..4dc9c7fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3675,6 +3675,9 @@ dependencies = [ "rkyv", "roaring", "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "rustls-webpki", "serde", "serde_json", "serial_test", @@ -3683,6 +3686,7 @@ dependencies = [ "thiserror 2.0.12", "thread_local", "tokio", + "tokio-rustls", "toml", "tracing", "tracing-appender", @@ -3867,10 +3871,14 @@ dependencies = [ "papaya", "rkyv", "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "rustls-webpki", "serde", "slotmap", "tikv-jemallocator", "tokio", + "tokio-rustls", "tokio-util", "tracing", "tracing-subscriber", @@ -6106,10 +6114,11 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -6129,18 +6138,19 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time", + "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.0" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", diff --git a/Cargo.toml b/Cargo.toml index 35de9074..a0e018cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,9 @@ quote = '1.0.39' rand = "0.9.1" rayon = '1.10.0' rkyv = '0.8.8' +rustls = { version = '0.23.31', default-features = false, features = ['logging', 'std', 'tls12'] } +rustls-pki-types = '1.12.0' +rustls-webpki = '0.103.4' serde = '1.0.217' serde_json = '1.0.140' serial_test = '3.2.0' @@ -112,6 +115,7 @@ thread_local = '1.1.8' tikv-jemallocator = '0.6.0' time = '0.3.41' tokio = '1.45.0' +tokio-rustls = { version = '0.26.2', default-features = false, features = ['logging', 'tls12'] } toml = '0.8.14' tracing-appender = '0.2.3' uuid = '1.16.0' diff --git a/README.md b/README.md index 2406fab6..6ed607ff 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,66 @@ sequenceDiagram ## Running +### Network topology + +Hyperion uses one game server which runs all game-related code (e.g. physics, game events). One or more proxies can connect to the game server. Players connect to one of the proxies. + +For development and testing purposes, it is okay to run one game server and one proxy on the same server. When generating keys, you will need to change the key and certificate file names used below to avoid file name conflicts. + +On a production environment, the game server and each proxy should run on separate servers for performance. + +### Generating keys and certificates + +The connection between the game server and the proxies are encrypted through mTLS to ensure that the connection is secure and authenticate the proxies. + +> [!WARNING] +> All private keys must be stored securely, and it is strongly recommended to generate the private keys on the server that will use them instead of transferring them over the Internet. Malicious proxies that have access to a private key can circumvent player authentication and can cause the game server to exhibit undefined behavior which can potentially lead to arbitrary code execution on the game server. If any private key has been compromised, redo this section to create new keys. + +#### Create a private certificate authority (CA) + +A server should be picked to store the certificate authority keys and will be referred to as the cetificate authority server. Since the game server and all proxies are considered to be trusted, any of these servers may be used for this purpose. + +On the certificate authority server, generate a key and certificate by running: + +```bash +openssl req -new -nodes -newkey rsa:4096 -keyout root_ca.pem -x509 -out root_ca.crt -days 365 +``` + +OpenSSL will ask for information when running the command. All fields can be left empty. + +The `-days` field specifies when the certificate will expire. It will expire in 365 days in the above command, but this can be modified as needed. + +`root_ca.crt` is the root CA cert and should be copied to the game server and all proxy servers. When running the game server or the proxy, make sure to pass `--root-ca-cert root_ca.crt` as a command line flag. + +#### Generate server keys and certificates + +Follow these instructions for the game server and each proxy server. The server will be referred to as the target server. + +On the target server, run: + +```bash +openssl req -nodes -newkey rsa:4096 -keyout server_private_key.pem -out server.csr +``` + +OpenSSL will ask for information when running the command. All fields can be left empty. + +Afterwards, transfer `server.csr` to the certificate authority server. On the certificate authority server, run: + +```bash +openssl x509 -req -in server.csr -CA root_ca.crt -CAkey root_ca.pem -CAcreateserial -out server.crt -days 365 -sha256 -extfile <(printf "subjectAltName=DNS:example.com,IP:127.0.0.1") +``` + +Replace `example.com` with the target server's domain name and replace `127.0.0.1` with the IP address that will be used by other servers to connect to the target server. +If the IP or domain provided is incorrect, connections will fail with the error "invalid peer certificate: certificate not valid for name ...". + +The `-days` field specifies when the certificate will expire. It will expire in 365 days in the above command, but this can be modified as needed. + +Then, transfer `server.crt` to the target server. + +`server.csr` and `server.crt` on the certificate authority server and `server.csr` on the target server are no longer needed and may be deleted. + +`server.crt` is the target server's certificate and `server_private_key.pem` is the target server's private key. When running the game server or the proxy, make sure to pass `--cert server.crt --private-key server_private_key.pem` as a command line flag. + ### Without cloning ```bash diff --git a/crates/hyperion-proxy-module/src/lib.rs b/crates/hyperion-proxy-module/src/lib.rs index 246207d9..9b8e4779 100644 --- a/crates/hyperion-proxy-module/src/lib.rs +++ b/crates/hyperion-proxy-module/src/lib.rs @@ -1,4 +1,4 @@ -use std::net::SocketAddr; +use std::{net::SocketAddr, path::Path}; use bevy::prelude::*; use hyperion::runtime::AsyncRuntime; @@ -36,12 +36,21 @@ fn update_proxy_address(trigger: Trigger<'_, SetProxyAddress>, runtime: Res<'_, let listener = TcpListener::bind(&proxy).await.unwrap(); tracing::info!("Listening on {proxy}"); - let server: SocketAddr = tokio::net::lookup_host(&server) + let addr: SocketAddr = tokio::net::lookup_host(&server) .await .unwrap() .next() .unwrap(); - hyperion_proxy::run_proxy(listener, server).await.unwrap(); + hyperion_proxy::run_proxy( + listener, + addr, + server.clone(), + Path::new("root_ca.crt"), + Path::new("proxy.crt"), + Path::new("proxy_private_key.pem"), + ) + .await + .unwrap(); }); } diff --git a/crates/hyperion-proxy/Cargo.toml b/crates/hyperion-proxy/Cargo.toml index 9f68d740..fed9c256 100644 --- a/crates/hyperion-proxy/Cargo.toml +++ b/crates/hyperion-proxy/Cargo.toml @@ -5,7 +5,11 @@ kanal = { workspace = true } papaya = { workspace = true } rkyv = { workspace = true } rustc-hash = { workspace = true } +rustls = { workspace = true } +rustls-pki-types = { workspace = true } +rustls-webpki = { workspace = true } tokio = { workspace = true, features = ["full", "tracing"] } +tokio-rustls = { workspace = true } tokio-util = { workspace = true, features = ["full"] } anyhow = { workspace = true } bvh = { workspace = true } diff --git a/crates/hyperion-proxy/src/lib.rs b/crates/hyperion-proxy/src/lib.rs index 91f59ae1..3fb025aa 100644 --- a/crates/hyperion-proxy/src/lib.rs +++ b/crates/hyperion-proxy/src/lib.rs @@ -16,16 +16,19 @@ clippy::future_not_send )] -use std::fmt::Debug; +use std::{fmt::Debug, path::Path, sync::Arc}; use anyhow::Context; use colored::Colorize; use hyperion_proto::ArchivedServerToProxyMessage; use rustc_hash::FxBuildHasher; +use rustls::{RootCertStore, client::ClientConfig}; +use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName, pem::PemObject}; use tokio::{ - io::{AsyncReadExt, BufReader}, + io::{AsyncRead, AsyncReadExt, BufReader}, net::{TcpStream, ToSocketAddrs}, }; +use tokio_rustls::TlsConnector; use tokio_util::net::Listener; use tracing::{Instrument, debug, error, info, info_span, instrument, trace, warn}; @@ -70,7 +73,43 @@ enum ShutdownType { pub async fn run_proxy( mut listener: impl HyperionListener, server_addr: impl ToSocketAddrs + Debug + Clone, + mut server_name: String, + root_ca_cert_path: &Path, + proxy_cert_path: &Path, + proxy_private_key_path: &Path, ) -> anyhow::Result<()> { + // Remove port + let Some(port_index) = server_name.rfind(':') else { + anyhow::bail!("server name is missing port"); + }; + server_name.truncate(port_index); + + let server_name = ServerName::try_from(server_name).context("failed to parse server name")?; + + let root_ca_cert = CertificateDer::from_pem_file(root_ca_cert_path) + .context("failed to load root certificate authority certificate")?; + let proxy_cert = CertificateDer::from_pem_file(proxy_cert_path) + .context("failed to load proxy certificate")?; + + let root_cert_store = Arc::new(RootCertStore { + roots: vec![ + webpki::anchor_from_trusted_cert(&root_ca_cert) + .context("failed to create trust anchor")? + .to_owned(), + ], + }); + + let cert_chain = vec![proxy_cert, root_ca_cert]; + let key_der = PrivateKeyDer::from_pem_file(proxy_private_key_path) + .context("failed to load proxy private key")?; + + let config = Arc::new( + ClientConfig::builder() + .with_root_certificates(root_cert_store) + .with_client_auth_cert(cert_chain, key_der) + .context("failed to create tls client config")?, + ); + let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(None); #[cfg(unix)] @@ -121,7 +160,7 @@ pub async fn run_proxy( let server_socket = connect(server_addr.clone()).await; server_socket.set_nodelay(true).unwrap(); - if let Err(e) = connect_to_server_and_run_proxy(&mut listener, server_socket, shutdown_rx.clone(), shutdown_tx.clone()).await { + if let Err(e) = connect_to_server_and_run_proxy(&mut listener, server_socket, server_name.clone(), config.clone(), shutdown_rx.clone(), shutdown_tx.clone()).await { error!("Error connecting to server: {e:?}"); } @@ -135,11 +174,20 @@ pub async fn run_proxy( async fn connect_to_server_and_run_proxy( listener: &mut impl HyperionListener, server_socket: TcpStream, + server_name: ServerName<'static>, + config: Arc, shutdown_rx: tokio::sync::watch::Receiver>, shutdown_tx: tokio::sync::watch::Sender>, ) -> anyhow::Result<()> { info!("🔗 Connected to server, accepting connections"); - let (server_read, server_write) = server_socket.into_split(); + + let connector = TlsConnector::from(config); + let server_stream = connector + .connect(server_name, server_socket) + .await + .context("failed to connect to game server")?; + + let (server_read, server_write) = tokio::io::split(server_stream); let server_sender = launch_server_writer(server_write); let player_registry = papaya::HashMap::default(); @@ -219,23 +267,23 @@ async fn connect_to_server_and_run_proxy( } } -struct IngressHandler { - server_read: BufReader, +struct IngressHandler { + server_read: BufReader, buffer: Vec, egress: BufferedEgress, } -impl Debug for IngressHandler { +impl Debug for IngressHandler { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ServerReader").finish() } } -impl IngressHandler { - pub fn new( - server_read: BufReader, - egress: BufferedEgress, - ) -> Self { +impl IngressHandler +where + R: AsyncRead + Unpin, +{ + pub fn new(server_read: BufReader, egress: BufferedEgress) -> Self { Self { server_read, egress, diff --git a/crates/hyperion-proxy/src/main.rs b/crates/hyperion-proxy/src/main.rs index f6542ea7..1b208d7c 100644 --- a/crates/hyperion-proxy/src/main.rs +++ b/crates/hyperion-proxy/src/main.rs @@ -26,6 +26,18 @@ struct Params { #[clap(short, long, default_value = "127.0.0.1:35565")] #[serde(default = "default_server")] server: String, + + /// The file path to the root certificate authority certificate + #[clap(long)] + root_ca_cert: PathBuf, + + /// The file path to the proxy certificate + #[clap(long)] + cert: PathBuf, + + /// The file path to the proxy private key + #[clap(long)] + private_key: PathBuf, } fn default_proxy_addr() -> String { @@ -128,14 +140,32 @@ async fn main() -> Result<(), Box> { ProxyAddress::Tcp(addr) => { let listener = TcpListener::bind(addr).await.unwrap(); let socket = NoDelayTcpListener { listener }; - run_proxy(socket, server_addr).await.unwrap(); + run_proxy( + socket, + server_addr, + params.server, + ¶ms.root_ca_cert, + ¶ms.cert, + ¶ms.private_key, + ) + .await + .unwrap(); } #[cfg(unix)] ProxyAddress::Unix(path) => { // remove file if already exists let _unused = tokio::fs::remove_file(path).await; let listener = UnixListener::bind(path).unwrap(); - run_proxy(listener, server_addr).await.unwrap(); + run_proxy( + listener, + server_addr, + "localhost:0".to_string(), + ¶ms.root_ca_cert, + ¶ms.cert, + ¶ms.private_key, + ) + .await + .unwrap(); } } }); diff --git a/crates/hyperion-proxy/src/server_sender.rs b/crates/hyperion-proxy/src/server_sender.rs index 581d14ba..81c40252 100644 --- a/crates/hyperion-proxy/src/server_sender.rs +++ b/crates/hyperion-proxy/src/server_sender.rs @@ -1,6 +1,7 @@ use std::io::IoSlice; use rkyv::util::AlignedVec; +use tokio::io::AsyncWrite; use tracing::{Instrument, trace_span, warn}; use crate::util::AsyncWriteVectoredExt; @@ -9,7 +10,7 @@ pub type ServerSender = kanal::AsyncSender; // todo: probably makes sense for caller to encode bytes #[must_use] -pub fn launch_server_writer(mut write: tokio::net::tcp::OwnedWriteHalf) -> ServerSender { +pub fn launch_server_writer(mut write: impl AsyncWrite + Unpin + Send + 'static) -> ServerSender { let (tx, rx) = kanal::bounded_async::(32_768); tokio::spawn( diff --git a/crates/hyperion/Cargo.toml b/crates/hyperion/Cargo.toml index 60e64b5a..da02210c 100644 --- a/crates/hyperion/Cargo.toml +++ b/crates/hyperion/Cargo.toml @@ -50,6 +50,9 @@ reqwest = { workspace = true } rkyv = { workspace = true } roaring = { workspace = true, features = ["simd"] } rustc-hash = { workspace = true } +rustls = { workspace = true } +rustls-pki-types = { workspace = true } +rustls-webpki = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sha2 = { workspace = true } @@ -57,6 +60,7 @@ simd-utils = { workspace = true } thiserror = { workspace = true } thread_local = { workspace = true } tokio = { workspace = true, features = ["full", "tracing"] } +tokio-rustls = { workspace = true } toml = { workspace = true } tracing = { workspace = true } tracing-tracy = { workspace = true } diff --git a/crates/hyperion/src/lib.rs b/crates/hyperion/src/lib.rs index befc2955..6a3cd3c6 100644 --- a/crates/hyperion/src/lib.rs +++ b/crates/hyperion/src/lib.rs @@ -26,7 +26,9 @@ pub const CHUNK_HEIGHT_SPAN: u32 = 384; // 512; // usually 384 -use std::{alloc::Allocator, fmt::Debug, io::Write, net::SocketAddr, sync::Arc, time::Duration}; +use std::{ + alloc::Allocator, fmt::Debug, io::Write, net::SocketAddr, path::Path, sync::Arc, time::Duration, +}; use bevy::prelude::*; use egress::EgressPlugin; @@ -34,6 +36,7 @@ pub use glam; #[cfg(unix)] use libc::{RLIMIT_NOFILE, getrlimit, setrlimit}; use libdeflater::CompressionLvl; +use rustls_pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject}; use storage::{LocalDb, SkinHandler}; use tracing::{info, warn}; pub use uuid; @@ -122,6 +125,42 @@ pub fn adjust_file_descriptor_limits(recommended_min: u64) -> std::io::Result<() Ok(()) } +#[derive(Resource)] +pub struct Crypto { + /// The root certificate authority's certificate + pub root_ca_cert: CertificateDer<'static>, + + /// The game server's certificate + pub cert: CertificateDer<'static>, + + /// The game server's private key + pub key: PrivateKeyDer<'static>, +} + +impl Crypto { + pub fn new( + root_ca_cert_path: &Path, + cert_path: &Path, + key_path: &Path, + ) -> Result { + Ok(Self { + root_ca_cert: CertificateDer::from_pem_file(root_ca_cert_path)?, + cert: CertificateDer::from_pem_file(cert_path)?, + key: PrivateKeyDer::from_pem_file(key_path)?, + }) + } +} + +impl Clone for Crypto { + fn clone(&self) -> Self { + Self { + root_ca_cert: self.root_ca_cert.clone(), + cert: self.cert.clone(), + key: self.key.clone_key(), + } + } +} + #[derive(Resource, Debug, Clone, PartialEq, Eq, Hash)] pub struct Endpoint(SocketAddr); @@ -213,8 +252,10 @@ impl Plugin for HyperionCore { app.add_plugins(CommandChannelPlugin); if let Some(address) = app.world().get_resource::() { + let crypto = app.world().resource::(); let command_channel = app.world().resource::(); - let egress_comm = init_proxy_comms(&runtime, command_channel.clone(), address.0); + let egress_comm = + init_proxy_comms(&runtime, command_channel.clone(), address.0, crypto.clone()); compose.io_buf_mut().add_egress_comm(egress_comm); } else { warn!("Endpoint was not set while loading HyperionCore"); diff --git a/crates/hyperion/src/net/proxy.rs b/crates/hyperion/src/net/proxy.rs index 0030d944..a2cdfe1c 100644 --- a/crates/hyperion/src/net/proxy.rs +++ b/crates/hyperion/src/net/proxy.rs @@ -1,16 +1,21 @@ //! Communication to a proxy which forwards packets to the players. -use std::{net::SocketAddr, process::Command}; +use std::{net::SocketAddr, process::Command, sync::Arc}; use bevy::prelude::*; use hyperion_proto::ArchivedProxyToServerMessage; use hyperion_utils::EntityExt; use rustc_hash::FxHashMap; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use rustls::{ + RootCertStore, + server::{ServerConfig, WebPkiClientVerifier}, +}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt}; +use tokio_rustls::TlsAcceptor; use tracing::{error, info, warn}; use crate::{ - ConnectionId, PacketDecoder, + ConnectionId, Crypto, PacketDecoder, command_channel::CommandChannel, net::Compose, runtime::AsyncRuntime, @@ -39,10 +44,7 @@ fn get_pid_from_port(port: u16) -> Result, std::io::Error> { Ok(pid) } -async fn handle_proxy_messages( - read: tokio::net::tcp::OwnedReadHalf, - command_channel: CommandChannel, -) { +async fn handle_proxy_messages(read: impl AsyncRead + Unpin, command_channel: CommandChannel) { let mut reader = ProxyReader::new(read); let mut player_packet_sender: FxHashMap = FxHashMap::default(); @@ -186,6 +188,7 @@ async fn handle_proxy_messages( async fn inner( socket: SocketAddr, + crypto: Crypto, mut server_to_proxy: tokio::sync::mpsc::UnboundedReceiver, command_channel: CommandChannel, ) { @@ -216,10 +219,30 @@ async fn inner( Err(e) => panic!("Failed to bind to address {socket}: {e}"), }; + let root_cert_store = Arc::new(RootCertStore { + roots: vec![ + webpki::anchor_from_trusted_cert(&crypto.root_ca_cert) + .unwrap() + .to_owned(), + ], + }); + + let config = ServerConfig::builder() + .with_client_cert_verifier( + WebPkiClientVerifier::builder(root_cert_store) + .build() + .unwrap(), + ) + .with_single_cert(vec![crypto.cert, crypto.root_ca_cert], crypto.key) + .unwrap(); + + let acceptor = TlsAcceptor::from(Arc::new(config)); + tokio::spawn( async move { loop { let (socket, _) = listener.accept().await.unwrap(); + socket.set_nodelay(true).unwrap(); let addr = match socket.peer_addr() { @@ -230,9 +253,21 @@ async fn inner( } }; + let command_channel = command_channel.clone(); + + let stream = match acceptor.accept(socket).await { + Ok(stream) => stream, + Err(e) => { + error!( + "failed to accept proxy connection from {addr}: tls accept failed: {e}" + ); + continue; + } + }; + info!("Proxy connection established on {addr}"); - let (read, mut write) = socket.into_split(); + let (read, mut write) = tokio::io::split(stream); let proxy_writer_task = tokio::spawn(async move { while let Some(bytes) = server_to_proxy.recv().await { @@ -266,20 +301,24 @@ pub fn init_proxy_comms( runtime: &AsyncRuntime, command_channel: CommandChannel, socket: SocketAddr, + crypto: Crypto, ) -> EgressComm { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - runtime.spawn(inner(socket, rx, command_channel)); + runtime.spawn(inner(socket, crypto, rx, command_channel)); EgressComm::from(tx) } #[derive(Debug)] -struct ProxyReader { - server_read: tokio::net::tcp::OwnedReadHalf, +struct ProxyReader { + server_read: R, buffer: Vec, } -impl ProxyReader { - pub fn new(server_read: tokio::net::tcp::OwnedReadHalf) -> Self { +impl ProxyReader +where + R: AsyncRead + Unpin, +{ + pub fn new(server_read: R) -> Self { Self { server_read, buffer: vec![0; 1024 * 1024], diff --git a/events/bedwars/src/lib.rs b/events/bedwars/src/lib.rs index 5f421fdc..4337b8c7 100644 --- a/events/bedwars/src/lib.rs +++ b/events/bedwars/src/lib.rs @@ -6,7 +6,7 @@ use std::net::SocketAddr; use bevy::prelude::*; -use hyperion::{Endpoint, HyperionCore, simulation::packet_state, spatial::Spatial}; +use hyperion::{Crypto, Endpoint, HyperionCore, simulation::packet_state, spatial::Spatial}; use hyperion_proxy_module::SetProxyAddress; use valence_text::IntoText; @@ -137,10 +137,11 @@ impl Plugin for BedwarsPlugin { } } -pub fn init_game(address: SocketAddr) -> anyhow::Result<()> { +pub fn init_game(address: SocketAddr, crypto: Crypto) -> anyhow::Result<()> { let mut app = App::new(); app.insert_resource(Endpoint::from(address)); + app.insert_resource(crypto); app.add_plugins((HyperionCore, BedwarsPlugin)); app.world_mut().trigger(SetProxyAddress { server: address.to_string(), diff --git a/events/bedwars/src/main.rs b/events/bedwars/src/main.rs index 9f3675f2..4391eeb4 100644 --- a/events/bedwars/src/main.rs +++ b/events/bedwars/src/main.rs @@ -1,7 +1,8 @@ -use std::net::SocketAddr; +use std::{net::SocketAddr, path::PathBuf}; use bedwars::init_game; use clap::Parser; +use hyperion::Crypto; use serde::Deserialize; use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt}; // use tracing_tracy::TracyLayer; @@ -22,6 +23,18 @@ struct Args { #[clap(short, long, default_value = "35565")] #[serde(default = "default_port")] port: u16, + + /// The file path to the root certificate authority's certificate + #[clap(long)] + root_ca_cert: PathBuf, + + /// The file path to the game server's certificate + #[clap(long)] + cert: PathBuf, + + /// The file path to the game server's private key + #[clap(long)] + private_key: PathBuf, } fn default_ip() -> String { @@ -70,6 +83,7 @@ fn main() { let address = format!("{ip}:{port}", ip = args.ip, port = args.port); let address = address.parse::().unwrap(); + let crypto = Crypto::new(&args.root_ca_cert, &args.cert, &args.private_key).unwrap(); - init_game(address).unwrap(); + init_game(address, crypto).unwrap(); }