Skip to content

Commit

Permalink
feat: port auth for ds (#1294)
Browse files Browse the repository at this point in the history
<!-- Please make sure there is an issue that this PR is correlated to. -->

## Changes

<!-- If there are frontend changes, please include screenshots. -->
  • Loading branch information
MasterPtato committed Oct 31, 2024
1 parent 062a85b commit f42c814
Show file tree
Hide file tree
Showing 356 changed files with 935 additions and 21,122 deletions.
17 changes: 16 additions & 1 deletion packages/api/actor/src/route/actors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,24 @@ pub async fn create(
routing: if let Some(routing) = p.routing {
match *routing {
models::ActorPortRouting {
game_guard: Some(_),
game_guard: Some(gg),
host: None,
} => ds::types::Routing::GameGuard {
protocol: p.protocol.api_into(),
authorization: match gg.authorization.as_deref() {
Some(models::ActorPortAuthorization {
bearer: Some(token),
..
}) => ds::types::PortAuthorization::Bearer(token.clone()),
Some(models::ActorPortAuthorization {
query: Some(query),
..
}) => ds::types::PortAuthorization::Query(
query.key.clone(),
query.value.clone(),
),
_ => ds::types::PortAuthorization::None,
},
},
models::ActorPortRouting {
game_guard: None,
Expand All @@ -130,6 +144,7 @@ pub async fn create(
} else {
ds::types::Routing::GameGuard {
protocol: p.protocol.api_into(),
authorization: ds::types::PortAuthorization::None,
}
}
}
Expand Down
8 changes: 2 additions & 6 deletions packages/api/actor/src/route/builds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,13 @@ pub async fn create_build(
let multipart_upload = body.multipart_upload.unwrap_or(false);

let kind = match body.kind {
None | Some(models::ActorBuildKind::DockerImage) => {
backend::build::BuildKind::DockerImage
}
None | Some(models::ActorBuildKind::DockerImage) => backend::build::BuildKind::DockerImage,
Some(models::ActorBuildKind::OciBundle) => backend::build::BuildKind::OciBundle,
Some(models::ActorBuildKind::Javascript) => backend::build::BuildKind::JavaScript,
};

let compression = match body.compression {
None | Some(models::ActorBuildCompression::None) => {
backend::build::BuildCompression::None
}
None | Some(models::ActorBuildCompression::None) => backend::build::BuildCompression::None,
Some(models::ActorBuildCompression::Lz4) => backend::build::BuildCompression::Lz4,
};

Expand Down
132 changes: 95 additions & 37 deletions packages/api/traefik-provider/src/route/game_guard/dynamic_servers.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
fmt::Write,
};

use api_helper::ctx::Ctx;
use rivet_operation::prelude::*;
use serde::{Deserialize, Serialize};
use ds::types::{PortAuthorizationType, PortAuthorization, GameGuardProtocol};

use crate::{auth::Auth, types};

// TODO: Rename to ProxiedPort since this is not 1:1 with servers
#[derive(sqlx::FromRow, Clone, Debug, Serialize, Deserialize)]
struct DynamicServer {
struct DynamicServerProxiedPort {
server_id: Uuid,
datacenter_id: Uuid,

label: String,
ip: String,

source: i64,
gg_port: i64,
port_name: String,
protocol: i64,

auth_type: Option<i64>,
auth_key: Option<String>,
auth_value: Option<String>,
}

impl DynamicServer {
impl DynamicServerProxiedPort {
fn parent_host(&self, config: &rivet_config::Config) -> GlobalResult<String> {
Ok(format!(
"lobby.{}.{}",
Expand All @@ -41,12 +48,12 @@ pub async fn build_ds(
dc_id: Uuid,
config: &mut types::TraefikConfigResponse,
) -> GlobalResult<()> {
let dynamic_servers = ctx
let proxied_ports = ctx
.cache()
.ttl(60_000)
.fetch_one_json("servers_ports", dc_id, |mut cache, dc_id| async move {
.fetch_one_json("ds_proxied_ports", dc_id, |mut cache, dc_id| async move {
let rows = sql_fetch_all!(
[ctx, DynamicServer]
[ctx, DynamicServerProxiedPort]
"
SELECT
s.server_id,
Expand All @@ -56,14 +63,21 @@ pub async fn build_ds(
pp.source,
gg.gg_port,
gg.port_name,
gg.protocol
gg.protocol,
gga.auth_type,
gga.auth_key,
gga.auth_value
FROM db_ds.server_proxied_ports AS pp
JOIN db_ds.servers AS s
ON pp.server_id = s.server_id
JOIN db_ds.server_ports_gg AS gg
ON
pp.server_id = gg.server_id AND
pp.label = CONCAT('ds_', REPLACE(gg.port_name, '-', '_'))
LEFT JOIN db_ds.server_ports_gg_auth AS gga
ON
gg.server_id = gga.server_id AND
gg.port_name = gga.port_name
WHERE
s.datacenter_id = $1 AND
s.destroy_ts IS NULL
Expand All @@ -78,19 +92,6 @@ pub async fn build_ds(
.await?
.unwrap_or_default();

// Process proxied ports
for dynamic_server in &dynamic_servers {
let server_id = dynamic_server.server_id;
let register_res =
ds_register_proxied_port(ctx.config(), server_id, dynamic_server, config);
match register_res {
Ok(_) => {}
Err(err) => {
tracing::error!(?err, "failed to register proxied port route")
}
}
}

config.http.middlewares.insert(
"ds-rate-limit".to_owned(),
types::TraefikMiddlewareHttp::RateLimit {
Expand All @@ -115,28 +116,46 @@ pub async fn build_ds(
},
);

// TODO: add middleware & services & ports
// TODO: same as jobs, watch out for namespaces
// Process proxied ports
for proxied_port in &proxied_ports {
if let Err(err) = ds_register_proxied_port(ctx.config(), proxied_port, config) {
tracing::error!(?err, "failed to register proxied port")
}
}

tracing::info!(
http_services = ?config.http.services.len(),
http_routers = ?config.http.routers.len(),
http_middlewares = ?config.http.middlewares.len(),
tcp_services = ?config.tcp.services.len(),
tcp_routers = ?config.tcp.routers.len(),
tcp_middlewares = ?config.tcp.middlewares.len(),
udp_services = ?config.udp.services.len(),
udp_routers = ?config.udp.routers.len(),
udp_middlewares = ?config.udp.middlewares.len(),
"dynamic servers traefik config"
);

Ok(())
}

#[tracing::instrument(skip(config))]
fn ds_register_proxied_port(
config: &rivet_config::Config,
server_id: Uuid,
proxied_port: &DynamicServer,
proxied_port: &DynamicServerProxiedPort,
traefik_config: &mut types::TraefikConfigResponse,
) -> GlobalResult<()> {
let ingress_port = proxied_port.gg_port;
let server_id = proxied_port.server_id;
let target_port_label = proxied_port.label.clone();
let service_id = format!("ds:{}:{}", server_id, target_port_label);
let proxy_protocol = unwrap!(ds::types::GameGuardProtocol::from_repr(
let service_id = format!("ds:{server_id}:{target_port_label}");
let proxy_protocol = unwrap!(GameGuardProtocol::from_repr(
proxied_port.protocol.try_into()?
));

// Insert the relevant service
match proxy_protocol {
ds::types::GameGuardProtocol::Http | ds::types::GameGuardProtocol::Https => {
GameGuardProtocol::Http | GameGuardProtocol::Https => {
traefik_config.http.services.insert(
service_id.clone(),
types::TraefikService {
Expand All @@ -153,7 +172,7 @@ fn ds_register_proxied_port(
},
);
}
ds::types::GameGuardProtocol::Tcp | ds::types::GameGuardProtocol::TcpTls => {
GameGuardProtocol::Tcp | GameGuardProtocol::TcpTls => {
traefik_config.tcp.services.insert(
service_id.clone(),
types::TraefikService {
Expand All @@ -167,7 +186,7 @@ fn ds_register_proxied_port(
},
);
}
ds::types::GameGuardProtocol::Udp => {
GameGuardProtocol::Udp => {
traefik_config.udp.services.insert(
service_id.clone(),
types::TraefikService {
Expand All @@ -185,7 +204,7 @@ fn ds_register_proxied_port(

// Insert the relevant router
match proxy_protocol {
ds::types::GameGuardProtocol::Http => {
GameGuardProtocol::Http => {
// Generate config
let middlewares = http_router_middlewares();
let rule = format_http_rule(config, proxied_port)?;
Expand All @@ -208,7 +227,7 @@ fn ds_register_proxied_port(
},
);
}
ds::types::GameGuardProtocol::Https => {
GameGuardProtocol::Https => {
// Generate config
let middlewares = http_router_middlewares();
let rule = format_http_rule(config, proxied_port)?;
Expand All @@ -234,7 +253,7 @@ fn ds_register_proxied_port(
},
);
}
ds::types::GameGuardProtocol::Tcp => {
GameGuardProtocol::Tcp => {
traefik_config.tcp.routers.insert(
format!("ds:{}:{}:tcp", server_id, target_port_label),
types::TraefikRouter {
Expand All @@ -247,7 +266,7 @@ fn ds_register_proxied_port(
},
);
}
ds::types::GameGuardProtocol::TcpTls => {
GameGuardProtocol::TcpTls => {
traefik_config.tcp.routers.insert(
format!("ds:{}:{}:tcp-tls", server_id, target_port_label),
types::TraefikRouter {
Expand All @@ -263,7 +282,7 @@ fn ds_register_proxied_port(
},
);
}
ds::types::GameGuardProtocol::Udp => {
GameGuardProtocol::Udp => {
traefik_config.udp.routers.insert(
format!("ds:{}:{}:udp", server_id, target_port_label),
types::TraefikRouter {
Expand All @@ -283,14 +302,49 @@ fn ds_register_proxied_port(

fn format_http_rule(
config: &rivet_config::Config,
proxied_port: &DynamicServer,
proxied_port: &DynamicServerProxiedPort,
) -> GlobalResult<String> {
Ok(format!("Host(`{}`)", proxied_port.hostname(config)?))
let authorization = {
let authorization_type = if let Some(auth_type) = proxied_port.auth_type {
unwrap!(PortAuthorizationType::from_repr(auth_type.try_into()?))
} else {
PortAuthorizationType::None
};

match authorization_type {
PortAuthorizationType::None => PortAuthorization::None,
PortAuthorizationType::Bearer => {
PortAuthorization::Bearer(unwrap!(proxied_port.auth_value.clone()))
}
PortAuthorizationType::Query => PortAuthorization::Query(
unwrap!(proxied_port.auth_key.clone()),
unwrap!(proxied_port.auth_value.clone()),
),
}
};

let mut rule = "(".to_string();

write!(&mut rule, "Host(`{}`)", proxied_port.hostname(config)?)?;

match authorization {
PortAuthorization::None => {}
PortAuthorization::Bearer(token) => {
write!(&mut rule, "&& Header(`Authorization`, `Bearer {}`)", escape_input(&token))?;
}
PortAuthorization::Query(key, value) => {
write!(&mut rule, "&& Query(`{}`, `{}`)", escape_input(&key), escape_input(&value))?;
}
}

rule.push_str(")");

Ok(rule)
}

fn build_tls_domains(
config: &rivet_config::Config,
proxied_port: &DynamicServer,
proxied_port: &DynamicServerProxiedPort,
) -> GlobalResult<Vec<types::TraefikTlsDomain>> {
// Derive TLS config. Jobs can specify their own ingress rules, so we
// need to derive which domains to use for the job.
Expand All @@ -312,3 +366,7 @@ fn http_router_middlewares() -> Vec<String> {

middlewares
}

fn escape_input(input: &str) -> String {
input.replace("`", "\\`")
}
4 changes: 2 additions & 2 deletions packages/services/admin/worker/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "cloud-worker"
name = "admin-worker"
version = "0.0.1"
edition = "2021"
authors = ["Rivet Gaming, LLC <developer@rivet.gg>"]
Expand All @@ -12,7 +12,7 @@ rivet-health-checks = { path = "../../../common/health-checks" }
rivet-metrics = { path = "../../../common/metrics" }
rivet-runtime = { path = "../../../common/runtime" }

cloud-game-token-create = { path = "../ops/game-token-create" }
cloud-game-token-create = { path = "../../cloud/ops/game-token-create" }
chrono = "0.4.31"
rivet-config = { version = "0.1.0", path = "../../../common/config" }
[dev-dependencies]
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE server_ports_gg_auth (
server_id UUID NOT NULL,
port_name UUID NOT NULL,
auth_type INT NOT NULL,
key TEXT,
value TEXT NOT NULL,

FOREIGN KEY (server_id, port_name) REFERENCES server_ports_gg (server_id, port_name),
PRIMARY KEY (server_id, port_name)
);
Loading

0 comments on commit f42c814

Please sign in to comment.