Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mm): add config to opt-in individual games for host networking & root containers #549

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions lib/bolt/config/src/ns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,8 +554,6 @@ pub struct Rivet {
pub cdn: Cdn,
#[serde(default)]
pub billing: Option<RivetBilling>,
#[serde(default)]
pub matchmaker: Option<RivetMatchmaker>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default)]
Expand All @@ -566,13 +564,6 @@ pub struct Telemetry {
pub disable: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default)]
#[serde(deny_unknown_fields)]
pub struct RivetMatchmaker {
#[serde(default)]
pub host_networking: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub enum RivetAccess {
Expand Down
12 changes: 0 additions & 12 deletions lib/bolt/core/src/context/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,18 +265,6 @@ impl ProjectContextData {
}
}

if self.ns().rivet.test.is_some()
&& !self.ns().pools.is_empty()
&& !self
.ns()
.rivet
.matchmaker
.as_ref()
.map_or(false, |mm| mm.host_networking)
{
panic!("must have host networking enabled if tests + pools are enabled (rivet.matchmaker.host_networking = true)");
}

// MARK: Billing emails
if self.ns().rivet.billing.is_some() {
assert!(
Expand Down
6 changes: 0 additions & 6 deletions lib/bolt/core/src/context/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,12 +783,6 @@ impl ServiceContextData {
env.push(("RIVET_ACCESS_TOKEN_LOGIN".into(), "1".into()));
}

if let Some(mm) = &project_ctx.ns().rivet.matchmaker {
if mm.host_networking {
env.push(("RIVET_HOST_NETWORKING".into(), "1".into()));
}
}

// Domains
if let Some(x) = project_ctx.domain_main() {
env.push(("RIVET_DOMAIN_MAIN".into(), x));
Expand Down
39 changes: 34 additions & 5 deletions lib/job-runner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ fn main() -> anyhow::Result<()> {
let nomad_alloc_dir = std::env::var("NOMAD_ALLOC_DIR").context("NOMAD_ALLOC_DIR")?;
let job_run_id = std::env::var("NOMAD_META_job_run_id").context("NOMAD_META_job_run_id")?;
let nomad_task_name = std::env::var("NOMAD_TASK_NAME").context("NOMAD_TASK_NAME")?;
let root_user_enabled = std::env::var("NOMAD_META_root_user_enabled")
.context("NOMAD_META_root_user_enabled")?
== "1";

let oci_bundle_path = format!("{}/oci-bundle", nomad_alloc_dir);
let container_id = fs::read_to_string(format!("{}/container-id", nomad_alloc_dir))
Expand All @@ -42,6 +45,32 @@ fn main() -> anyhow::Result<()> {
};
let log_shipper_thread = log_shipper.spawn();

// Validate OCI bundle
let oci_bundle_str =
fs::read_to_string(&oci_bundle_path).context("failed to read OCI bundle")?;
let oci_bundle = serde_json::from_str::<serde_json::Value>(&oci_bundle_str)
.context("failed to parse OCI bundle")?;
let (Some(uid), Some(gid)) = (
oci_bundle["process"]["user"]["uid"].as_i64(),
oci_bundle["process"]["user"]["gid"].as_i64(),
) else {
bail!("missing uid or gid in OCI bundle")
};
if !root_user_enabled && (uid == 0 || gid == 0) {
send_message(
&msg_tx,
None,
log_shipper::StreamType::StdErr,
format!("Server is attempting to run as root user or group (uid: {uid}, gid: {gid})"),
);
send_message(
&msg_tx,
None,
log_shipper::StreamType::StdErr,
format!("See https://rivet.gg/docs/dynamic-servers/concepts/docker-root-user"),
);
}

// Spawn runc container
println!(
"Starting container {} with OCI bundle {}",
Expand Down Expand Up @@ -172,7 +201,7 @@ fn ship_logs(
if err.first_throttle_in_window {
if send_message(
&msg_tx,
&mut throttle_error,
Some(&mut throttle_error),
stream_type,
format_rate_limit(err.time_remaining),
) {
Expand All @@ -184,7 +213,7 @@ fn ship_logs(
if err.first_throttle_in_window {
if send_message(
&msg_tx,
&mut throttle_error,
Some(&mut throttle_error),
stream_type,
format_rate_limit(err.time_remaining),
) {
Expand Down Expand Up @@ -224,7 +253,7 @@ fn ship_logs(
}
}

if send_message(&msg_tx, &mut throttle_error, stream_type, message) {
if send_message(&msg_tx, Some(&mut throttle_error), stream_type, message) {
break;
}
}
Expand All @@ -238,7 +267,7 @@ fn ship_logs(
/// Returns true if receiver is disconnected
fn send_message(
msg_tx: &mpsc::SyncSender<log_shipper::ReceivedMessage>,
throttle_error: &mut throttle::Throttle,
throttle_error: Option<&mut throttle::Throttle>,
stream_type: log_shipper::StreamType,
message: String,
) -> bool {
Expand All @@ -258,7 +287,7 @@ fn send_message(
}) {
Result::Ok(_) => {}
Err(mpsc::TrySendError::Full(_)) => {
if throttle_error.tick().is_ok() {
if throttle_error.map_or(true, |x| x.tick().is_ok()) {
eprintln!("log shipper buffer full, logs are being dropped");
}
}
Expand Down
6 changes: 6 additions & 0 deletions proto/backend/matchmaker.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import "proto/common.proto";
import "proto/backend/captcha.proto";
import "proto/backend/region.proto";

// MARK: Game Config
message GameConfig {
bool host_networking_enabled = 1;
bool root_user_enabled = 2;
}

// MARK: Game Namespace Config
message NamespaceConfig {
uint32 lobby_count_max = 1;
Expand Down
23 changes: 23 additions & 0 deletions svc/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions svc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ members = [
"pkg/load-test/standalone/mm-sustain",
"pkg/load-test/standalone/sqlx",
"pkg/load-test/standalone/watch-requests",
"pkg/mm-config/ops/game-get",
"pkg/mm-config/ops/game-upsert",
"pkg/mm-config/ops/lobby-group-get",
"pkg/mm-config/ops/lobby-group-resolve-name-id",
"pkg/mm-config/ops/lobby-group-resolve-version",
Expand Down
1 change: 1 addition & 0 deletions svc/pkg/faker/ops/game/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ faker-game-namespace = { path = "../game-namespace" }
faker-game-version = { path = "../game-version" }
faker-team = { path = "../team" }
game-create = { path = "../../../game/ops/create" }
mm-config-game-upsert = { path = "../../../mm-config/ops/game-upsert" }

[dev-dependencies]
chirp-worker = { path = "../../../../../lib/chirp/worker" }
Expand Down
13 changes: 12 additions & 1 deletion svc/pkg/faker/ops/game/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use proto::backend::pkg::*;
use proto::backend::{self, pkg::*};
use rivet_operation::prelude::*;

#[operation(name = "faker-game")]
Expand Down Expand Up @@ -28,6 +28,17 @@ async fn handle(
})
.await?;

op!([ctx] mm_config_game_upsert {
game_id: game_create_res.game_id,
config: Some(backend::matchmaker::GameConfig {
// Required for testing
host_networking_enabled: true,
// Will never be tested
root_user_enabled: false,
})
})
.await?;

let mut namespace_ids = Vec::<common::Uuid>::new();
let mut version_ids = Vec::<common::Uuid>::new();
if !ctx.skip_namespaces_and_versions {
Expand Down
2 changes: 1 addition & 1 deletion svc/pkg/game/ops/version-validate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ util-mm = { package = "rivet-util-mm", path = "../../../mm/util" }
external-request-validate = { path = "../../../external/ops/request-validate" }
game-version-get = { path = "../version-get" }
game-version-list = { path = "../version-list" }
mm-config-game-get = { path = "../../../mm-config/ops/game-get" }
module-version-get = { path = "../../../module/ops/version-get" }
region-get = { path = "../../../region/ops/get" }
tier-list = { path = "../../../tier/ops/list" }

[dev-dependencies]
chirp-worker = { path = "../../../../../lib/chirp/worker" }

faker-game = { path = "../../../faker/ops/game" }
28 changes: 17 additions & 11 deletions svc/pkg/game/ops/version-validate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ async fn handle(
errors.push(util::err_path!["display-name", "invalid"]);
}

// Get game config
let game_res = op!([ctx] mm_config_game_get {
game_ids: vec![*game_id],
})
.await?;
let mm_game_config = unwrap_ref!(unwrap!(game_res.games.first()).config);

// Validate display name uniqueness
{
let version_list_res = op!([ctx] game_version_list {
Expand Down Expand Up @@ -789,11 +796,18 @@ async fn handle(
let network_mode = unwrap!(LobbyRuntimeNetworkMode::from_i32(
docker_config.network_mode
));
let host_networking_enabled =
std::env::var("RIVET_HOST_NETWORKING").map_or(false, |v| v == "1");
// Validate ports
if host_networking_enabled || !matches!(network_mode, LobbyRuntimeNetworkMode::Host)
if !mm_game_config.host_networking_enabled
&& matches!(network_mode, LobbyRuntimeNetworkMode::Host)
{
errors.push(util::err_path![
"config",
"matchmaker",
"game-modes",
lobby_group_label,
"host-networking-disabled",
]);
} else {
let mut unique_port_labels = HashSet::<String>::new();
let mut unique_ports = HashSet::<(u32, i32)>::new();
let mut ranges = Vec::<PortRange>::new();
Expand Down Expand Up @@ -1026,14 +1040,6 @@ async fn handle(
}
}
}
} else {
errors.push(util::err_path![
"config",
"matchmaker",
"game-modes",
lobby_group_label,
"host-networking-disabled",
]);
}
} else {
errors.push(util::err_path![
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE games (
game_id UUID PRIMARY KEY,
host_networking_enabled BOOLEAN NOT NULL DEFAULT FALSE,
root_user_enabled BOOLEAN NOT NULL DEFAULT FALSE
);

17 changes: 17 additions & 0 deletions svc/pkg/mm-config/ops/game-get/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "mm-config-game-get"
version = "0.0.1"
edition = "2021"
authors = ["Rivet Gaming, LLC <developer@rivet.gg>"]
license = "Apache-2.0"

[dependencies]
chirp-client = { path = "../../../../../lib/chirp/client" }
rivet-operation = { path = "../../../../../lib/operation/core" }

[dependencies.sqlx]
version = "0.7"
default-features = false

[dev-dependencies]
chirp-worker = { path = "../../../../../lib/chirp/worker" }
7 changes: 7 additions & 0 deletions svc/pkg/mm-config/ops/game-get/Service.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[service]
name = "mm-config-game-get"

[runtime]
kind = "rust"

[operation]
47 changes: 47 additions & 0 deletions svc/pkg/mm-config/ops/game-get/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use proto::backend::{self, pkg::*};
use rivet_operation::prelude::*;

#[derive(sqlx::FromRow)]
struct GameRow {
game_id: Uuid,
host_networking_enabled: bool,
root_user_enabled: bool,
}

#[operation(name = "mm-config-game-get")]
pub async fn handle(
ctx: OperationContext<mm_config::game_get::Request>,
) -> GlobalResult<mm_config::game_get::Response> {
let game_ids = ctx
.game_ids
.iter()
.map(common::Uuid::as_uuid)
.collect::<Vec<_>>();

let rows = sql_fetch_all!(
[ctx, GameRow]
"
SELECT game_id, host_networking_enabled, root_user_enabled
FROM db_mm_config.games
WHERE game_id = ANY($1)
",
&game_ids,
)
.await?;

let games = game_ids
.iter()
.map(|game_id| {
let row = rows.iter().find(|row| row.game_id == *game_id);
mm_config::game_get::response::Game {
game_id: Some((*game_id).into()),
config: Some(backend::matchmaker::GameConfig {
host_networking_enabled: row.map_or(false, |row| row.host_networking_enabled),
root_user_enabled: row.map_or(false, |row| row.root_user_enabled),
}),
}
})
.collect();

Ok(mm_config::game_get::Response { games })
}
Loading
Loading