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: port auth for ds #1294

Closed
wants to merge 1 commit into from
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
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
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
Loading