From 3789551a166e8faae13a83455ebab7436e410d2d Mon Sep 17 00:00:00 2001 From: itowlson Date: Thu, 11 Dec 2025 16:51:07 +1300 Subject: [PATCH 1/2] Load Postgres certs from runtime config Signed-off-by: itowlson --- crates/factor-outbound-pg/src/client.rs | 32 +++++++----- crates/factor-outbound-pg/src/lib.rs | 12 +++-- .../factor-outbound-pg/src/runtime_config.rs | 4 ++ .../factor-outbound-pg/tests/factor_test.rs | 4 +- crates/runtime-config/src/lib.rs | 20 ++++++-- crates/runtime-config/src/pg.rs | 49 +++++++++++++++++++ pg-app/runtime-config.toml | 2 + 7 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 crates/factor-outbound-pg/src/runtime_config.rs create mode 100644 crates/runtime-config/src/pg.rs create mode 100644 pg-app/runtime-config.toml diff --git a/crates/factor-outbound-pg/src/client.rs b/crates/factor-outbound-pg/src/client.rs index 6a8bab7da9..4cc590c646 100644 --- a/crates/factor-outbound-pg/src/client.rs +++ b/crates/factor-outbound-pg/src/client.rs @@ -18,9 +18,10 @@ const CONNECTION_POOL_CACHE_CAPACITY: u64 = 16; /// A factory object for Postgres clients. This abstracts /// details of client creation such as pooling. #[async_trait] -pub trait ClientFactory: Default + Send + Sync + 'static { +pub trait ClientFactory: Send + Sync + 'static { /// The type of client produced by `get_client`. type Client: Client; + fn new(root_certificates: Vec>) -> Self; /// Gets a client from the factory. async fn get_client(&self, address: &str) -> Result; } @@ -28,24 +29,26 @@ pub trait ClientFactory: Default + Send + Sync + 'static { /// A `ClientFactory` that uses a connection pool per address. pub struct PooledTokioClientFactory { pools: moka::sync::Cache, + root_certificates: Vec>, } -impl Default for PooledTokioClientFactory { - fn default() -> Self { +#[async_trait] +impl ClientFactory for PooledTokioClientFactory { + type Client = deadpool_postgres::Object; + + fn new(root_certificates: Vec>) -> Self { Self { pools: moka::sync::Cache::new(CONNECTION_POOL_CACHE_CAPACITY), + root_certificates, } } -} - -#[async_trait] -impl ClientFactory for PooledTokioClientFactory { - type Client = deadpool_postgres::Object; async fn get_client(&self, address: &str) -> Result { let pool = self .pools - .try_get_with_by_ref(address, || create_connection_pool(address)) + .try_get_with_by_ref(address, || { + create_connection_pool(address, &self.root_certificates) + }) .map_err(ArcError) .context("establishing PostgreSQL connection pool")?; @@ -54,7 +57,10 @@ impl ClientFactory for PooledTokioClientFactory { } /// Creates a Postgres connection pool for the given address. -fn create_connection_pool(address: &str) -> Result { +fn create_connection_pool( + address: &str, + root_certificates: &[Vec], +) -> Result { let config = address .parse::() .context("parsing Postgres connection string")?; @@ -68,7 +74,11 @@ fn create_connection_pool(address: &str) -> Result { let mgr = if config.get_ssl_mode() == SslMode::Disable { deadpool_postgres::Manager::from_config(config, NoTls, mgr_config) } else { - let builder = TlsConnector::builder(); + let mut builder = TlsConnector::builder(); + for cert_bytes in root_certificates { + builder.add_root_certificate(native_tls::Certificate::from_pem(cert_bytes)?); + } + let connector = MakeTlsConnector::new(builder.build()?); deadpool_postgres::Manager::from_config(config, connector, mgr_config) }; diff --git a/crates/factor-outbound-pg/src/lib.rs b/crates/factor-outbound-pg/src/lib.rs index b3a433946e..ba593390d8 100644 --- a/crates/factor-outbound-pg/src/lib.rs +++ b/crates/factor-outbound-pg/src/lib.rs @@ -1,5 +1,6 @@ pub mod client; mod host; +pub mod runtime_config; mod types; use std::sync::Arc; @@ -18,7 +19,7 @@ pub struct OutboundPgFactor { } impl Factor for OutboundPgFactor { - type RuntimeConfig = (); + type RuntimeConfig = runtime_config::RuntimeConfig; type AppState = Arc; type InstanceBuilder = InstanceState; @@ -36,9 +37,14 @@ impl Factor for OutboundPgFactor { fn configure_app( &self, - _ctx: ConfigureAppContext, + ctx: ConfigureAppContext, ) -> anyhow::Result { - Ok(Arc::new(CF::default())) + let certificates = match ctx.runtime_config() { + Some(rc) => rc.certificates.clone(), + None => vec![], + }; + // let certificates = certificate_paths.iter().map(std::fs::read).collect::, _>>()?; + Ok(Arc::new(CF::new(certificates))) } fn prepare( diff --git a/crates/factor-outbound-pg/src/runtime_config.rs b/crates/factor-outbound-pg/src/runtime_config.rs new file mode 100644 index 0000000000..10a2ac587c --- /dev/null +++ b/crates/factor-outbound-pg/src/runtime_config.rs @@ -0,0 +1,4 @@ +#[derive(Default)] +pub struct RuntimeConfig { + pub certificates: Vec>, +} diff --git a/crates/factor-outbound-pg/tests/factor_test.rs b/crates/factor-outbound-pg/tests/factor_test.rs index 364e62a7f4..181ca1b415 100644 --- a/crates/factor-outbound-pg/tests/factor_test.rs +++ b/crates/factor-outbound-pg/tests/factor_test.rs @@ -105,13 +105,15 @@ async fn exercise_query() -> anyhow::Result<()> { } // TODO: We can expand this mock to track calls and simulate return values -#[derive(Default)] pub struct MockClientFactory {} pub struct MockClient {} #[async_trait] impl ClientFactory for MockClientFactory { type Client = MockClient; + fn new(_: Vec>) -> Self { + Self {} + } async fn get_client(&self, _address: &str) -> Result { Ok(MockClient {}) } diff --git a/crates/runtime-config/src/lib.rs b/crates/runtime-config/src/lib.rs index 4c0d6f4b1f..354d81eb76 100644 --- a/crates/runtime-config/src/lib.rs +++ b/crates/runtime-config/src/lib.rs @@ -24,6 +24,7 @@ use spin_sqlite as sqlite; use spin_trigger::cli::UserProvidedPath; use toml::Value; +mod pg; pub mod variables; /// The default state directory for the trigger. @@ -137,9 +138,13 @@ where let outbound_networking = runtime_config_dir .clone() .map(OutboundNetworkingSpinRuntimeConfig::new); - let key_value_resolver = key_value_config_resolver(runtime_config_dir, state_dir.clone()); + let key_value_resolver = + key_value_config_resolver(runtime_config_dir.clone(), state_dir.clone()); let sqlite_resolver = sqlite_config_resolver(state_dir.clone()) .context("failed to resolve sqlite runtime config")?; + let pg_resolver = pg::PgConfigResolver { + base_dir: runtime_config_dir.clone(), + }; let toml = toml_resolver.toml(); let log_dir = toml_resolver.log_dir()?; @@ -150,6 +155,7 @@ where &key_value_resolver, outbound_networking.as_ref(), &sqlite_resolver, + &pg_resolver, ); // Note: all valid fields in the runtime config must have been referenced at @@ -302,6 +308,7 @@ pub struct TomlRuntimeConfigSource<'a, 'b> { key_value: &'a key_value::RuntimeConfigResolver, outbound_networking: Option<&'a OutboundNetworkingSpinRuntimeConfig>, sqlite: &'a sqlite::RuntimeConfigResolver, + pg_resolver: &'a pg::PgConfigResolver, } impl<'a, 'b> TomlRuntimeConfigSource<'a, 'b> { @@ -310,12 +317,14 @@ impl<'a, 'b> TomlRuntimeConfigSource<'a, 'b> { key_value: &'a key_value::RuntimeConfigResolver, outbound_networking: Option<&'a OutboundNetworkingSpinRuntimeConfig>, sqlite: &'a sqlite::RuntimeConfigResolver, + pg_resolver: &'a pg::PgConfigResolver, ) -> Self { Self { toml: toml_resolver, key_value, outbound_networking, sqlite, + pg_resolver, } } } @@ -349,8 +358,13 @@ impl FactorRuntimeConfigSource for TomlRuntimeConfigSource<'_, } impl FactorRuntimeConfigSource for TomlRuntimeConfigSource<'_, '_> { - fn get_runtime_config(&mut self) -> anyhow::Result> { - Ok(None) + fn get_runtime_config( + &mut self, + ) -> anyhow::Result::RuntimeConfig>> { + Ok(Some( + self.pg_resolver + .runtime_config_from_toml(&self.toml.table)?, + )) } } diff --git a/crates/runtime-config/src/pg.rs b/crates/runtime-config/src/pg.rs new file mode 100644 index 0000000000..319eef7dca --- /dev/null +++ b/crates/runtime-config/src/pg.rs @@ -0,0 +1,49 @@ +use std::path::PathBuf; + +use serde::Deserialize; +use spin_factor_outbound_pg::runtime_config::RuntimeConfig; +use spin_factors::runtime_config::toml::GetTomlValue; + +pub struct PgConfigResolver { + pub(crate) base_dir: Option, // must have a value if any certs, but we need to deref it lazily +} + +impl PgConfigResolver { + pub fn runtime_config_from_toml( + &self, + table: &impl GetTomlValue, + ) -> anyhow::Result { + let Some(table) = table.get("postgres").and_then(|t| t.as_table()) else { + return Ok(Default::default()); + }; + + let table: RuntimeConfigTable = RuntimeConfigTable::deserialize(table.clone())?; + + let certificate_paths = table + .root_certificates + .iter() + .map(PathBuf::from) + .collect::>(); + + let has_relative = certificate_paths.iter().any(|p| p.is_relative()); + + let certificate_paths = match (has_relative, self.base_dir.as_ref()) { + (false, _) => certificate_paths, + (true, None) => anyhow::bail!("the runtime config file contains relative certificate paths, but we could not determine the runtime config directory for them to be relative to"), + (true, Some(base)) => certificate_paths.into_iter().map(|p| base.join(p)).collect::>(), + }; + + let certificates = certificate_paths + .iter() + .map(std::fs::read) + .collect::, _>>()?; + + Ok(RuntimeConfig { certificates }) + } +} + +#[derive(Deserialize)] +struct RuntimeConfigTable { + #[serde(default)] + root_certificates: Vec, +} diff --git a/pg-app/runtime-config.toml b/pg-app/runtime-config.toml new file mode 100644 index 0000000000..0d3a81faf8 --- /dev/null +++ b/pg-app/runtime-config.toml @@ -0,0 +1,2 @@ +[postgres] +root_certificates = ["postgres-ssl/ca.crt"] From 376076cf4f6340e6113b1fb78ef490cc351d9821 Mon Sep 17 00:00:00 2001 From: itowlson Date: Mon, 15 Dec 2025 14:52:30 +1300 Subject: [PATCH 2/2] Manual test case Signed-off-by: itowlson --- tests/manual/pg-ssl-root-certs/.gitignore | 4 + tests/manual/pg-ssl-root-certs/Cargo.lock | 1173 +++++++++++++++++ tests/manual/pg-ssl-root-certs/Cargo.toml | 16 + tests/manual/pg-ssl-root-certs/local-db.sh | 113 ++ tests/manual/pg-ssl-root-certs/make-certs.sh | 39 + .../pg-ssl-root-certs}/runtime-config.toml | 0 tests/manual/pg-ssl-root-certs/setup.md | 54 + tests/manual/pg-ssl-root-certs/spin.toml | 20 + tests/manual/pg-ssl-root-certs/src/lib.rs | 25 + 9 files changed, 1444 insertions(+) create mode 100644 tests/manual/pg-ssl-root-certs/.gitignore create mode 100644 tests/manual/pg-ssl-root-certs/Cargo.lock create mode 100644 tests/manual/pg-ssl-root-certs/Cargo.toml create mode 100755 tests/manual/pg-ssl-root-certs/local-db.sh create mode 100755 tests/manual/pg-ssl-root-certs/make-certs.sh rename {pg-app => tests/manual/pg-ssl-root-certs}/runtime-config.toml (100%) create mode 100644 tests/manual/pg-ssl-root-certs/setup.md create mode 100644 tests/manual/pg-ssl-root-certs/spin.toml create mode 100644 tests/manual/pg-ssl-root-certs/src/lib.rs diff --git a/tests/manual/pg-ssl-root-certs/.gitignore b/tests/manual/pg-ssl-root-certs/.gitignore new file mode 100644 index 0000000000..7bf49926c2 --- /dev/null +++ b/tests/manual/pg-ssl-root-certs/.gitignore @@ -0,0 +1,4 @@ +target/ +.spin/ +pg +postgres-ssl diff --git a/tests/manual/pg-ssl-root-certs/Cargo.lock b/tests/manual/pg-ssl-root-certs/Cargo.lock new file mode 100644 index 0000000000..a3047b0eaf --- /dev/null +++ b/tests/manual/pg-ssl-root-certs/Cargo.lock @@ -0,0 +1,1173 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pg-app" +version = "0.1.0" +dependencies = [ + "anyhow", + "spin-sdk", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "postgres-protocol" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + +[[package]] +name = "postgres_range" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6dce28dc5ba143d8eb157b62aac01ae5a1c585c40792158b720e86a87642101" +dependencies = [ + "postgres-protocol", + "postgres-types", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.111", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "routefinder" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0971d3c8943a6267d6bd0d782fdc4afa7593e7381a92a3df950ff58897e066b5" +dependencies = [ + "smartcow", + "smartstring", +] + +[[package]] +name = "rust_decimal" +version = "1.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" +dependencies = [ + "arrayvec", + "num-traits", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smartcow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" +dependencies = [ + "smartstring", +] + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "spin-executor" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd83f9f07676e2a8a1e9eaccd245344a67296712c24828bd69f3d11c127cd59d" +dependencies = [ + "futures", + "once_cell", + "wasi", +] + +[[package]] +name = "spin-macro" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "607df2c3b413f7086fa47a5402daa0d51640ba9b9b8e8955c43a58bd4057d2f4" +dependencies = [ + "anyhow", + "bytes", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "spin-sdk" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4952d7c69dbfbeff54d62f0e3b3e14c105b80871d2100557f7b1b1429beffc24" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "chrono", + "form_urlencoded", + "futures", + "http", + "once_cell", + "postgres_range", + "routefinder", + "rust_decimal", + "serde", + "serde_json", + "spin-executor", + "spin-macro", + "thiserror", + "uuid", + "wasi", + "wit-bindgen 0.43.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.13.1+wasi-0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f43d1c36145feb89a3e61aa0ba3e582d976a8ab77f1474aa0adb80800fe0cf8" +dependencies = [ + "wit-bindgen-rt 0.24.0", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.111", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.235.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc393c395cb621367ff02d854179882b9a351b4e0c93d1397e6090b53a5c2a" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.235.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b055604ba04189d54b8c0ab2c2fc98848f208e103882d5c0b984f045d5ea4d20" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.235.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161296c618fa2d63f6ed5fffd1112937e803cb9ec71b32b01a76321555660917" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a18712ff1ec5bd09da500fe1e91dec11256b310da0ff33f8b4ec92b927cf0c6" +dependencies = [ + "wit-bindgen-rt 0.43.0", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "wit-bindgen-core" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c53468e077362201de11999c85c07c36e12048a990a3e0d69da2bd61da355d0" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0780cf7046630ed70f689a098cd8d56c5c3b22f2a7379bbdb088879963ff96" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd734226eac1fd7c450956964e3a9094c9cee65e9dafdf126feef8c0096db65" +dependencies = [ + "bitflags", + "futures", + "once_cell", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531ebfcec48e56473805285febdb450e270fa75b2dacb92816861d0473b4c15f" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.111", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7852bf8a9d1ea80884d26b864ddebd7b0c7636697c6ca10f4c6c93945e023966" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.111", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.235.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a57a11109cc553396f89f3a38a158a97d0b1adaec113bd73e0f64d30fb601f" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.235.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1f95a87d03a33e259af286b857a95911eb46236a0f726cbaec1227b3dfc67a" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] diff --git a/tests/manual/pg-ssl-root-certs/Cargo.toml b/tests/manual/pg-ssl-root-certs/Cargo.toml new file mode 100644 index 0000000000..f7c0a6c17b --- /dev/null +++ b/tests/manual/pg-ssl-root-certs/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "pg-app" +authors = ["Kate Goldenring "] +description = "" +version = "0.1.0" +rust-version = "1.78" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1" +spin-sdk = "5.1.0" + +[workspace] diff --git a/tests/manual/pg-ssl-root-certs/local-db.sh b/tests/manual/pg-ssl-root-certs/local-db.sh new file mode 100755 index 0000000000..9d427fa90e --- /dev/null +++ b/tests/manual/pg-ssl-root-certs/local-db.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +# Local PostgreSQL setup script for macOS +# Keeps data in ./pg directory + +set -e # Exit on error + +# Configuration +PG_DIR="./pg" +PG_DATA="$PG_DIR/data" +PG_LOG="$PG_DIR/postgres.log" +DB_NAME="mydb" +PORT=5432 + +echo "🐘 PostgreSQL Local Setup" +echo "==========================" + +# Check if PostgreSQL is installed +if ! command -v postgres &> /dev/null; then + echo "❌ PostgreSQL is not installed." + echo "Install it with: brew install postgresql@16" + exit 1 +fi + +# Create pg directory if it doesn't exist +mkdir -p "$PG_DIR" + +# Initialize database if data directory doesn't exist +if [ ! -d "$PG_DATA" ]; then + echo "📁 Initializing new PostgreSQL database..." + initdb -D "$PG_DATA" --encoding=UTF8 --locale=C + + echo "✅ Database initialized" +else + echo "✅ Database already exists" +fi + +# Check if PostgreSQL is already running +if pg_ctl -D "$PG_DATA" status &> /dev/null; then + echo "⚠️ PostgreSQL is already running" +else + # Start PostgreSQL + echo "🚀 Starting PostgreSQL..." + pg_ctl -D "$PG_DATA" -l "$PG_LOG" -o "-p $PORT" start + + # Wait for PostgreSQL to be ready + echo "⏳ Waiting for PostgreSQL to be ready..." + sleep 2 + + # Check if server is ready + until pg_isready -p $PORT &> /dev/null; do + echo " Still waiting..." + sleep 1 + done + + echo "✅ PostgreSQL is running on port $PORT" +fi + +# Check if database exists, create if not +if ! psql -p $PORT -lqt | cut -d \| -f 1 | grep -qw "$DB_NAME"; then + echo "📊 Creating database '$DB_NAME'..." + createdb -p $PORT "$DB_NAME" + echo "✅ Database created" +else + echo "✅ Database '$DB_NAME' already exists" +fi + +# Check if users table exists +TABLE_EXISTS=$(psql -p $PORT -d "$DB_NAME" -tAc "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'users');") + +if [ "$TABLE_EXISTS" = "f" ]; then + echo "👥 Creating users table and adding sample data..." + + psql -p $PORT -d "$DB_NAME" << EOF +-- Create users table +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + age INTEGER NOT NULL +); + +-- Insert sample data +INSERT INTO users (name, age) VALUES + ('Alice Johnson', 28), + ('Bob Smith', 35), + ('Charlie Brown', 42); + +-- Display the data +SELECT * FROM users; +EOF + + echo "✅ Users table created and populated" +else + echo "✅ Users table already exists" + echo "📋 Current users:" + psql -p $PORT -d "$DB_NAME" -c "SELECT * FROM users;" +fi + +echo "" +echo "==========================" +echo "🎉 Setup complete!" +echo "" +echo "Connection details:" +echo " Host: localhost" +echo " Port: $PORT" +echo " Database: $DB_NAME" +echo " Data directory: $PG_DATA" +echo "" +echo "Useful commands:" +echo " Connect: psql -p $PORT -d $DB_NAME" +echo " Stop: pg_ctl -D $PG_DATA stop" +echo " Status: pg_ctl -D $PG_DATA status" +echo " View logs: tail -f $PG_LOG" \ No newline at end of file diff --git a/tests/manual/pg-ssl-root-certs/make-certs.sh b/tests/manual/pg-ssl-root-certs/make-certs.sh new file mode 100755 index 0000000000..8f3e947d6e --- /dev/null +++ b/tests/manual/pg-ssl-root-certs/make-certs.sh @@ -0,0 +1,39 @@ +# Create a directory for certificates +mkdir -p postgres-ssl +cd postgres-ssl + + +# 1. Generate CA private key +openssl genrsa -out ca.key 4096 + +# 2. Generate CA certificate (self-signed, valid for 10 years) +openssl req -new -x509 -key ca.key -out ca.crt -days 3650 \ + -subj "/C=US/ST=California/L=San Francisco/O=MyOrganization/OU=Certificate Authority/CN=MyOrganization Root CA" + +# 3. Generate server private key +openssl genrsa -out server.key 2048 + +# 4. Generate server certificate signing request (CSR) +openssl req -new -key server.key -out server.csr \ + -subj "/C=US/ST=California/L=San Francisco/O=MyOrganization/OU=Development/CN=localhost" + +# 5. Sign the server certificate with the CA +openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ + -out server.crt -days 365 + +# 6. Set proper permissions +chmod 600 server.key ca.key +chmod 644 server.crt ca.crt + +# 7. Clean up the CSR (optional) +rm server.csr + +cd .. + +psql -d postgres -c "ALTER SYSTEM SET ssl = 'on';" \ + -c "ALTER SYSTEM SET ssl_cert_file = '${PWD}/postgres-ssl/server.crt';" \ + -c "ALTER SYSTEM SET ssl_key_file = '${PWD}/postgres-ssl/server.key';" \ + -c "ALTER SYSTEM SET ssl_ca_file = '${PWD}/postgres-ssl/ca.crt';" \ + -c "SELECT pg_reload_conf();" + +echo "consider running 'brew services restart postgresql' or 'sudo systemctl restart postgresql' to ensure changes take effect." \ No newline at end of file diff --git a/pg-app/runtime-config.toml b/tests/manual/pg-ssl-root-certs/runtime-config.toml similarity index 100% rename from pg-app/runtime-config.toml rename to tests/manual/pg-ssl-root-certs/runtime-config.toml diff --git a/tests/manual/pg-ssl-root-certs/setup.md b/tests/manual/pg-ssl-root-certs/setup.md new file mode 100644 index 0000000000..d00891f804 --- /dev/null +++ b/tests/manual/pg-ssl-root-certs/setup.md @@ -0,0 +1,54 @@ +# Setting up Postgres with Certs + +## Prerequisite + +PostgreSQL installed and on your PATH. If it isn't, the setup script will display instructions for `brew install`-ing it. + +## Create the DB and seed it with 3 rows + +```sh +./local-db.sh +``` + +Test base case without TLS: + +```sh +spin build --up +``` + +Test +```console +$ curl localhost:3000 +Done +``` + +## Enable TLS in Postgres + +1. Create SSL Certificates and Configure Postgres + +To create certs and configure PG to use them, run `make-certs.sh` to generate self-signed SSL certificates for Postgres. This script will create a directory `postgres-ssl` containing the necessary certificate files. + + +2. Update the `pg_hba.conf` file to require SSL connections. + +```sh + # Update pg_hba.conf to require TLS + cat >> "pg/data/pg_hba.conf" << EOF +hostssl all all 127.0.0.1/32 trust +hostssl all all ::1/128 trust +EOF +``` + +Then reload: +```sh +pg_ctl reload -D pg/data +``` + +Restart spin to create a new PG connection that will now require TLS. Now calls will fail without TLS: + +```console +$ curl localhost:3000 +Error::ConnectionFailed("Error occurred while creating a new object: error performing TLS handshake: The certificate was not trusted.\n\nCaused by:\n 0: error performing TLS handshake: The certificate was not trusted.\n 1: The certificate was not trusted.")% +``` + +Then run `spin up --runtime-config-file runtime-config.toml` to confirm that it works when the custom root certs are loaded. diff --git a/tests/manual/pg-ssl-root-certs/spin.toml b/tests/manual/pg-ssl-root-certs/spin.toml new file mode 100644 index 0000000000..4bfa8d6be3 --- /dev/null +++ b/tests/manual/pg-ssl-root-certs/spin.toml @@ -0,0 +1,20 @@ +#:schema https://schemas.spinframework.dev/spin/manifest-v2/latest.json + +spin_manifest_version = 2 + +[application] +name = "pg-app" +version = "0.1.0" +authors = ["Kate Goldenring "] +description = "" + +[[trigger.http]] +route = "/..." +component = "pg-app" + +[component.pg-app] +source = "target/wasm32-wasip1/release/pg_app.wasm" +allowed_outbound_hosts = ["postgres://localhost"] +[component.pg-app.build] +command = "cargo build --target wasm32-wasip1 --release" +watch = ["src/**/*.rs", "Cargo.toml"] diff --git a/tests/manual/pg-ssl-root-certs/src/lib.rs b/tests/manual/pg-ssl-root-certs/src/lib.rs new file mode 100644 index 0000000000..76eaffa009 --- /dev/null +++ b/tests/manual/pg-ssl-root-certs/src/lib.rs @@ -0,0 +1,25 @@ +use spin_sdk::http::{IntoResponse, Request, Response}; +use spin_sdk::http_component; +use spin_sdk::pg4::{Connection, Decode}; + +/// A simple Spin HTTP component. +#[http_component] +fn handle_pg_app(_req: Request) -> anyhow::Result { + do_db_operation()?; + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body("Done") + .build()) +} + +fn do_db_operation() -> anyhow::Result<()> { + // Works without TLS + let db = Connection::open("host=localhost dbname=mydb")?; + // Fails when TLS is configured AND CA is set + // let db = Connection::open("host=localhost dbname=mydb sslmode=require")?; + let query_result = db.query("SELECT COUNT(*) FROM users", &[])?; + let count = i64::decode(&query_result.rows[0][0])?; + println!("User count: {}", count); + Ok(()) +} \ No newline at end of file