Skip to content

Commit 20dec4d

Browse files
committed
Support for the PG wire protocol
1 parent 0f70e63 commit 20dec4d

File tree

10 files changed

+961
-38
lines changed

10 files changed

+961
-38
lines changed

Cargo.lock

+353-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ paste = "1.0"
204204
percent-encoding = "2.3"
205205
petgraph = { version = "0.6.5", default-features = false }
206206
pin-project-lite = "0.2.9"
207+
pgwire = { version = "0.28.0", features = ["server-api"] }
207208
postgres-types = "0.2.5"
208209
pretty_assertions = { version = "1.4", features = ["unstable"] }
209210
proc-macro2 = "1.0"
@@ -216,6 +217,7 @@ rand08 = { package = "rand", version = "0.8" }
216217
rand = "0.9"
217218
rayon = "1.8"
218219
rayon-core = "1.11.0"
220+
rcgen = { version = "0.13.1", features = ["pem", "x509-parser", "crypto", "ring"] }
219221
regex = "1"
220222
reqwest = { version = "0.12", features = ["stream", "json"] }
221223
ron = "0.8"
@@ -224,6 +226,8 @@ rust_decimal = { version = "1.29.1", features = ["db-tokio-postgres"] }
224226
rustc-demangle = "0.1.21"
225227
rustc-hash = "2"
226228
rustyline = { version = "12.0.0", features = [] }
229+
rustls-pki-types = "1.11.0"
230+
rustls = "0.23.26"
227231
scoped-tls = "1.0.1"
228232
scopeguard = "1.1.0"
229233
second-stack = "0.3"
@@ -255,6 +259,7 @@ termcolor = "1.2.0"
255259
thin-vec = "0.2.13"
256260
thiserror = "1.0.37"
257261
tokio = { version = "1.37", features = ["full"] }
262+
tokio-rustls = "0.26.2"
258263
tokio_metrics = { version = "0.4.0" }
259264
tokio-postgres = { version = "0.7.8", features = ["with-chrono-0_4"] }
260265
tokio-stream = "0.1.17"

crates/client-api-messages/src/name.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ pub enum SetDefaultDomainResult {
150150
///
151151
/// Must match the regex `^[a-z0-9]+(-[a-z0-9]+)*$`
152152
#[derive(Clone, Debug, serde_with::DeserializeFromStr, serde_with::SerializeDisplay)]
153-
pub struct DatabaseName(String);
153+
pub struct DatabaseName(pub String);
154154

155155
impl AsRef<str> for DatabaseName {
156156
fn as_ref(&self) -> &str {

crates/client-api/src/auth.rs

+40-6
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,30 @@ impl TokenClaims {
132132
}
133133

134134
impl SpacetimeAuth {
135+
pub fn from_claims(
136+
ctx: &(impl NodeDelegate + ControlStateDelegate + ?Sized),
137+
claims: SpacetimeIdentityClaims,
138+
) -> axum::response::Result<Self> {
139+
let claims = TokenClaims {
140+
issuer: claims.issuer,
141+
subject: claims.subject,
142+
audience: claims.audience,
143+
};
144+
145+
let creds = {
146+
let token = claims.encode_and_sign(ctx.jwt_auth_provider()).map_err(log_and_500)?;
147+
SpacetimeCreds::from_signed_token(token)
148+
};
149+
let identity = claims.id();
150+
151+
Ok(Self {
152+
creds,
153+
identity,
154+
subject: claims.subject,
155+
issuer: claims.issuer,
156+
})
157+
}
158+
135159
/// Allocate a new identity, and mint a new token for it.
136160
pub async fn alloc(ctx: &(impl NodeDelegate + ControlStateDelegate + ?Sized)) -> axum::response::Result<Self> {
137161
// Generate claims with a random subject.
@@ -186,6 +210,8 @@ pub trait JwtAuthProvider: Sync + Send + TokenSigner {
186210
///
187211
/// The `/identity/public-key` route calls this method to return the public key to callers.
188212
fn public_key_bytes(&self) -> &[u8];
213+
/// Return the private key used to verify JWTs, as the bytes of a PEM private key file.
214+
fn private_key_bytes(&self) -> &[u8];
189215
}
190216

191217
pub struct JwtKeyAuthProvider<TV: TokenValidator + Send + Sync> {
@@ -222,6 +248,10 @@ impl<TV: TokenValidator + Send + Sync> TokenSigner for JwtKeyAuthProvider<TV> {
222248
impl<TV: TokenValidator + Send + Sync> JwtAuthProvider for JwtKeyAuthProvider<TV> {
223249
type TV = TV;
224250

251+
fn validator(&self) -> &Self::TV {
252+
&self.validator
253+
}
254+
225255
fn local_issuer(&self) -> &str {
226256
&self.local_issuer
227257
}
@@ -230,8 +260,8 @@ impl<TV: TokenValidator + Send + Sync> JwtAuthProvider for JwtKeyAuthProvider<TV
230260
&self.keys.public_pem
231261
}
232262

233-
fn validator(&self) -> &Self::TV {
234-
&self.validator
263+
fn private_key_bytes(&self) -> &[u8] {
264+
&self.keys.private_pem
235265
}
236266
}
237267

@@ -260,6 +290,13 @@ mod tests {
260290
}
261291
}
262292

293+
pub async fn validate_token<S: NodeDelegate>(
294+
state: &S,
295+
token: &str,
296+
) -> Result<SpacetimeIdentityClaims, TokenValidationError> {
297+
state.jwt_auth_provider().validator().validate_token(token).await
298+
}
299+
263300
pub struct SpacetimeAuthHeader {
264301
auth: Option<SpacetimeAuth>,
265302
}
@@ -272,10 +309,7 @@ impl<S: NodeDelegate + Send + Sync> axum::extract::FromRequestParts<S> for Space
272309
return Ok(Self { auth: None });
273310
};
274311

275-
let claims = state
276-
.jwt_auth_provider()
277-
.validator()
278-
.validate_token(&creds.token)
312+
let claims = validate_token(state, &creds.token)
279313
.await
280314
.map_err(AuthorizationRejection::Custom)?;
281315

crates/client-api/src/routes/database.rs

+25-11
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::auth::{
66
SpacetimeIdentityToken,
77
};
88
use crate::routes::subscribe::generate_random_connection_id;
9-
use crate::util::{ByteStringBody, NameOrIdentity};
9+
pub use crate::util::{ByteStringBody, NameOrIdentity};
1010
use crate::{log_and_500, ControlStateDelegate, DatabaseDef, NodeDelegate};
1111
use axum::body::{Body, Bytes};
1212
use axum::extract::{Path, Query, State};
@@ -25,10 +25,11 @@ use spacetimedb::host::ReducerOutcome;
2525
use spacetimedb::host::UpdateDatabaseResult;
2626
use spacetimedb::identity::Identity;
2727
use spacetimedb::messages::control_db::{Database, HostType};
28+
use spacetimedb_client_api_messages::http::SqlStmtResult;
2829
use spacetimedb_client_api_messages::name::{self, DatabaseName, DomainName, PublishOp, PublishResult};
2930
use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9;
3031
use spacetimedb_lib::identity::AuthCtx;
31-
use spacetimedb_lib::sats;
32+
use spacetimedb_lib::{sats, ProductValue};
3233

3334
use super::subscribe::handle_websocket;
3435

@@ -381,19 +382,19 @@ async fn worker_ctx_find_database(
381382

382383
#[derive(Deserialize)]
383384
pub struct SqlParams {
384-
name_or_identity: NameOrIdentity,
385+
pub name_or_identity: NameOrIdentity,
385386
}
386387

387388
#[derive(Deserialize)]
388389
pub struct SqlQueryParams {}
389390

390-
pub async fn sql<S>(
391-
State(worker_ctx): State<S>,
392-
Path(SqlParams { name_or_identity }): Path<SqlParams>,
393-
Query(SqlQueryParams {}): Query<SqlQueryParams>,
394-
Extension(auth): Extension<SpacetimeAuth>,
395-
body: String,
396-
) -> axum::response::Result<impl IntoResponse>
391+
pub async fn sql_direct<S>(
392+
worker_ctx: S,
393+
SqlParams { name_or_identity }: SqlParams,
394+
_params: SqlQueryParams,
395+
auth: SpacetimeAuth,
396+
sql: String,
397+
) -> axum::response::Result<Vec<SqlStmtResult<ProductValue>>>
397398
where
398399
S: NodeDelegate + ControlStateDelegate,
399400
{
@@ -413,7 +414,20 @@ where
413414
.await
414415
.map_err(log_and_500)?
415416
.ok_or(StatusCode::NOT_FOUND)?;
416-
let json = host.exec_sql(auth, database, body).await?;
417+
host.exec_sql(auth, database, sql).await
418+
}
419+
420+
pub async fn sql<S>(
421+
State(worker_ctx): State<S>,
422+
Path(name_or_identity): Path<SqlParams>,
423+
Query(params): Query<SqlQueryParams>,
424+
Extension(auth): Extension<SpacetimeAuth>,
425+
body: String,
426+
) -> axum::response::Result<impl IntoResponse>
427+
where
428+
S: NodeDelegate + ControlStateDelegate,
429+
{
430+
let json = sql_direct(worker_ctx, name_or_identity, params, auth, body).await?;
417431

418432
let total_duration = json.iter().fold(0, |acc, x| acc + x.total_duration_micros);
419433

crates/core/src/auth/mod.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct JwtKeys {
1515
pub public: DecodingKey,
1616
pub public_pem: Box<[u8]>,
1717
pub private: EncodingKey,
18+
pub private_pem: Box<[u8]>,
1819
pub kid: Option<String>,
1920
}
2021

@@ -23,15 +24,17 @@ impl JwtKeys {
2324
/// respectively.
2425
///
2526
/// The key files must be PEM encoded ECDSA P256 keys.
26-
pub fn new(public_pem: impl Into<Box<[u8]>>, private_pem: &[u8]) -> anyhow::Result<Self> {
27+
pub fn new(public_pem: impl Into<Box<[u8]>>, private_pem: impl Into<Box<[u8]>>) -> anyhow::Result<Self> {
2728
let public_pem = public_pem.into();
29+
let private_pem = private_pem.into();
2830
let public = DecodingKey::from_ec_pem(&public_pem)?;
29-
let private = EncodingKey::from_ec_pem(private_pem)?;
31+
let private = EncodingKey::from_ec_pem(&private_pem)?;
3032

3133
Ok(Self {
3234
public,
3335
private,
3436
public_pem,
37+
private_pem,
3538
kid: None,
3639
})
3740
}
@@ -75,7 +78,7 @@ pub struct EcKeyPair {
7578
impl TryFrom<EcKeyPair> for JwtKeys {
7679
type Error = anyhow::Error;
7780
fn try_from(pair: EcKeyPair) -> anyhow::Result<Self> {
78-
JwtKeys::new(pair.public_key_bytes, &pair.private_key_bytes)
81+
JwtKeys::new(pair.public_key_bytes, pair.private_key_bytes)
7982
}
8083
}
8184

crates/standalone/Cargo.toml

+6
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,22 @@ http.workspace = true
3838
log.workspace = true
3939
openssl.workspace = true
4040
parse-size.workspace = true
41+
pgwire.workspace = true
4142
prometheus.workspace = true
4243
scopeguard.workspace = true
44+
serde.workspace = true
4345
serde_json.workspace = true
4446
sled.workspace = true
4547
socket2.workspace = true
4648
thiserror.workspace = true
4749
tokio.workspace = true
50+
tokio-rustls.workspace = true
4851
tower-http.workspace = true
4952
toml.workspace = true
5053
tracing = { workspace = true, features = ["release_max_level_debug"] }
54+
rustls-pki-types.workspace = true
55+
rcgen.workspace = true
56+
rustls.workspace = true
5157

5258
[target.'cfg(not(target_env = "msvc"))'.dependencies]
5359
tikv-jemallocator = {workspace = true}

crates/standalone/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod control_db;
2+
pub mod pg_server;
23
pub mod subcommands;
34
pub mod util;
45
pub mod version;

0 commit comments

Comments
 (0)