From 667f434189fc81855adc9835ec1b3eac2677e396 Mon Sep 17 00:00:00 2001 From: red binder Date: Wed, 12 Aug 2026 04:57:53 +0200 Subject: [PATCH] feat(grpc): add support for maintenance checks --- Cargo.lock | 2 + rnex-server-api-grpc/auth.proto | 13 ++++ rnex-server-api/src/lib.rs | 4 ++ .../rnex-auth/src/auth_handler.rs | 14 +++- rnex-server-nex-modules/rnex-auth/src/lib.rs | 20 ++++++ rnex-server/backend-auth/Cargo.toml | 2 + rnex-server/backend-auth/src/admin.rs | 72 +++++++++++++++++++ rnex-server/backend-auth/src/main.rs | 25 ++++++- 8 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 rnex-server-api-grpc/auth.proto create mode 100644 rnex-server/backend-auth/src/admin.rs diff --git a/Cargo.lock b/Cargo.lock index 53437d4..8c84590 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2682,7 +2682,9 @@ version = "0.1.0" dependencies = [ "rnex-auth", "rnex-server", + "rnex-server-api", "tokio", + "tonic", ] [[package]] diff --git a/rnex-server-api-grpc/auth.proto b/rnex-server-api-grpc/auth.proto new file mode 100644 index 0000000..ef2c688 --- /dev/null +++ b/rnex-server-api-grpc/auth.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package auth; + +message MaintenanceState { + bool enabled = 1; +} + +message Empty {} + +service AuthAdminService { + rpc SetMaintenance(MaintenanceState) returns (Empty); + rpc GetMaintenance(Empty) returns (MaintenanceState); +} \ No newline at end of file diff --git a/rnex-server-api/src/lib.rs b/rnex-server-api/src/lib.rs index c1a3fed..9484722 100644 --- a/rnex-server-api/src/lib.rs +++ b/rnex-server-api/src/lib.rs @@ -4,3 +4,7 @@ pub mod meta { pub mod gatherings { tonic::include_proto!("gatherings"); } + +pub mod auth { + tonic::include_proto!("auth"); +} diff --git a/rnex-server-nex-modules/rnex-auth/src/auth_handler.rs b/rnex-server-nex-modules/rnex-auth/src/auth_handler.rs index 5ae783a..c2b788b 100644 --- a/rnex-server-nex-modules/rnex-auth/src/auth_handler.rs +++ b/rnex-server-nex-modules/rnex-auth/src/auth_handler.rs @@ -18,7 +18,7 @@ use rnex_rmc::{ use rnex_server::PassthroughInitModule; use tracing::{info, warn}; -use crate::AuthManager; +use crate::{AuthManager, is_maintenance}; #[derive(Debug)] #[rmc_struct(AuthProtocol)] @@ -188,6 +188,10 @@ impl Auth for AuthHandler { &self, name: String, ) -> Result<(QResult, PID, Vec, ConnectionDataOld, String), ErrorCode> { + if is_maintenance() { + return Err(ErrorCode::RendezVous_GameServerMaintenance); + } + let (pid, ticket) = self.generate_ticket_from_name(&name).await?; let result = QResult::success(ErrorCode::Core_Unknown); @@ -231,6 +235,10 @@ impl Auth for AuthHandler { name: String, _extra_data: Any, ) -> Result<(QResult, PID, Vec, ConnectionData, String, String), ErrorCode> { + if is_maintenance() { + return Err(ErrorCode::RendezVous_GameServerMaintenance); + } + let (pid, key, ticket) = self.generate_ticket_from_name_string_user_key(&name).await?; let result = QResult::success(Core_Unknown); @@ -296,6 +304,10 @@ impl Auth for AuthHandler { name: String, _extra_data: Any, ) -> Result<(QResult, PID, Vec, ConnectionData, String), ErrorCode> { + if is_maintenance() { + return Err(ErrorCode::RendezVous_GameServerMaintenance); + } + let (pid, ticket) = self.generate_ticket_from_name(&name).await?; let result = QResult::success(ErrorCode::Core_Unknown); diff --git a/rnex-server-nex-modules/rnex-auth/src/lib.rs b/rnex-server-nex-modules/rnex-auth/src/lib.rs index c0b576e..acce36a 100644 --- a/rnex-server-nex-modules/rnex-auth/src/lib.rs +++ b/rnex-server-nex-modules/rnex-auth/src/lib.rs @@ -7,6 +7,26 @@ use rnex_server::{ConnectionInitData, RnexManager, RnexModule, WeakPassthroughIn use tracing::info; use crate::auth_handler::AuthHandler; +use std::sync::{ + Arc, OnceLock, + atomic::{AtomicBool, Ordering}, +}; + +static MAINTENANCE: OnceLock> = OnceLock::new(); + +pub fn maintenance_flag() -> Arc { + MAINTENANCE + .get_or_init(|| Arc::new(AtomicBool::new(false))) + .clone() +} + +pub fn set_maintenance(on: bool) { + maintenance_flag().store(on, Ordering::Relaxed); +} + +pub fn is_maintenance() -> bool { + maintenance_flag().load(Ordering::Relaxed) +} #[derive(Debug)] pub struct AuthManager { diff --git a/rnex-server/backend-auth/Cargo.toml b/rnex-server/backend-auth/Cargo.toml index 03886ed..feac777 100644 --- a/rnex-server/backend-auth/Cargo.toml +++ b/rnex-server/backend-auth/Cargo.toml @@ -25,3 +25,5 @@ big_pid = [] rnex-server = {path = ".."} rnex-auth = {path = "../../rnex-server-nex-modules/rnex-auth"} tokio = { version = "1.52.3", features = ["rt-multi-thread"] } +rnex-server-api = { path = "../../rnex-server-api" } +tonic = { version = "0.14.6", features = ["transport"] } \ No newline at end of file diff --git a/rnex-server/backend-auth/src/admin.rs b/rnex-server/backend-auth/src/admin.rs new file mode 100644 index 0000000..ac5e08e --- /dev/null +++ b/rnex-server/backend-auth/src/admin.rs @@ -0,0 +1,72 @@ +use std::net::SocketAddr; + +use rnex_auth::{is_maintenance, set_maintenance}; +use rnex_server_api::auth::{ + auth_admin_service_server::{AuthAdminService, AuthAdminServiceServer}, + MaintenanceState, Empty, +}; +use tonic::{Request, Response, Status, transport::Server}; + +pub struct AdminGrpc { + token: Option, +} + +impl AdminGrpc { + pub fn new(token: Option) -> Self { + Self { token } + } + + fn authorize(&self, request: &Request) -> Result<(), Status> { + let Some(expected) = &self.token else { + return Ok(()); + }; + + let Some(value) = request.metadata().get("authorization") else { + return Err(Status::unauthenticated("missing authorization header")); + }; + + let header = value + .to_str() + .map_err(|_| Status::unauthenticated("authorization header must be ASCII"))?; + + let supplied = header + .strip_prefix("Bearer ") + .or_else(|| header.strip_prefix("bearer ")) + .ok_or_else(|| Status::unauthenticated("authorization header must use Bearer scheme"))?; + + if supplied == expected { + Ok(()) + } else { + Err(Status::unauthenticated("invalid admin token")) + } + } +} + +#[tonic::async_trait] +impl AuthAdminService for AdminGrpc { + async fn set_maintenance( + &self, + request: Request, + ) -> Result, Status> { + self.authorize(&request)?; + set_maintenance(request.into_inner().enabled); + Ok(Response::new(Empty {})) + } + + async fn get_maintenance( + &self, + request: Request, + ) -> Result, Status> { + self.authorize(&request)?; + Ok(Response::new(MaintenanceState { + enabled: is_maintenance(), + })) + } +} + +pub async fn serve(addr: SocketAddr, token: Option) -> Result<(), tonic::transport::Error> { + Server::builder() + .add_service(AuthAdminServiceServer::new(AdminGrpc::new(token))) + .serve(addr) + .await +} \ No newline at end of file diff --git a/rnex-server/backend-auth/src/main.rs b/rnex-server/backend-auth/src/main.rs index c2dc657..d99fbe6 100644 --- a/rnex-server/backend-auth/src/main.rs +++ b/rnex-server/backend-auth/src/main.rs @@ -1,9 +1,32 @@ +mod admin; + +use std::net::SocketAddr; + use rnex_auth::AuthModule; use rnex_server::{ConnectionInitData, launch_rnex_module_server}; + +fn admin_addr() -> SocketAddr { + std::env::var("RNEX_AUTH_ADMIN_GRPC_ADDR") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or_else(|| SocketAddr::from(([127, 0, 0, 1], 50052))) +} + #[tokio::main] async fn main() { + let token = std::env::var("RNEX_AUTH_ADMIN_GRPC_TOKEN").ok(); + + let admin_task = tokio::spawn(async move { + if let Err(err) = admin::serve(admin_addr(), token).await { + rnex_server::tracing::error!(%err, "auth admin gRPC server stopped"); + } + }); + launch_rnex_module_server! { ConnectionInitData; AuthModule } -} + + admin_task.abort(); + let _ = admin_task.await; +} \ No newline at end of file