From c75fc3f2bc99c8c7b602cb78dacb237a23d6a1bc Mon Sep 17 00:00:00 2001 From: chunningham Date: Tue, 11 Jul 2023 15:53:29 +0200 Subject: [PATCH] impl host key derivation from static secret (#146) * impl host key derivation from static secret * update readme, note on backing up the secret * remove libp2p, now unnecessary in main bin * require secret be provided in config --- Cargo.lock | 144 +++++++++++++++++++++------------------- Cargo.toml | 1 - README.md | 17 ++++- kepler-core/Cargo.toml | 2 +- kepler-core/src/db.rs | 93 ++++++++++++++++++-------- kepler-core/src/keys.rs | 78 ++++++++++++++++++++++ kepler-core/src/lib.rs | 2 + kepler.toml | 4 ++ src/auth_guards.rs | 53 +-------------- src/config.rs | 43 +++++++++++- src/lib.rs | 17 +++-- src/routes/mod.rs | 30 ++++++--- 12 files changed, 313 insertions(+), 171 deletions(-) create mode 100644 kepler-core/src/keys.rs diff --git a/Cargo.lock b/Cargo.lock index 2aba24a3..3f968428 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,7 +508,7 @@ dependencies = [ "md-5 0.10.5", "pin-project-lite", "sha1", - "sha2 0.10.6", + "sha2 0.10.7", "tracing", ] @@ -747,7 +747,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -932,6 +932,15 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "tinyvec", +] + [[package]] name = "buf_redux" version = "0.8.4" @@ -1692,7 +1701,7 @@ checksum = "074c4ae82880d60a25048cd3bf2e8aaaa881922d7c73fbb9ec29fc67fa0d33e4" dependencies = [ "async-trait", "bech32", - "bs58", + "bs58 0.4.0", "chrono", "iref", "serde", @@ -1712,7 +1721,7 @@ checksum = "562670fedf756b20c047dcf4ef88d020c5c86c2115f954fb3aef072952015ba4" dependencies = [ "anyhow", "async-trait", - "bs58", + "bs58 0.4.0", "chrono", "json-patch", "reqwest", @@ -1778,9 +1787,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", @@ -1863,7 +1872,7 @@ dependencies = [ "base16ct", "crypto-bigint 0.4.9", "der 0.6.1", - "digest 0.10.6", + "digest 0.10.7", "ff", "generic-array 0.14.7", "group", @@ -2379,7 +2388,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2810,7 +2819,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.6", + "sha2 0.10.7", "sha3 0.10.7", ] @@ -2849,7 +2858,6 @@ dependencies = [ "kepler-core", "kepler-lib", "lazy_static", - "libp2p", "opentelemetry", "opentelemetry-jaeger", "pin-project", @@ -3142,9 +3150,9 @@ checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "libp2p" -version = "0.51.3" +version = "0.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f210d259724eae82005b5c48078619b7745edb7b76de370b03f8ba59ea103097" +checksum = "38039ba2df4f3255842050845daef4a004cc1f26da03dbc645535088b51910ef" dependencies = [ "bytes", "futures", @@ -3162,9 +3170,9 @@ dependencies = [ [[package]] name = "libp2p-allow-block-list" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510daa05efbc25184458db837f6f9a5143888f1caa742426d92e1833ddd38a50" +checksum = "55b46558c5c0bf99d3e2a1a38fd54ff5476ca66dd1737b12466a1824dd219311" dependencies = [ "libp2p-core", "libp2p-identity", @@ -3174,9 +3182,9 @@ dependencies = [ [[package]] name = "libp2p-connection-limits" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4caa33f1d26ed664c4fe2cca81a08c8e07d4c1c04f2f4ac7655c2dd85467fda0" +checksum = "d45dd90e8f0e1fa59e85ff5316dd4d1ac41a9a507e79cda1b0e9b7be43ad1a56" dependencies = [ "libp2p-core", "libp2p-identity", @@ -3186,9 +3194,9 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.39.2" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c1df63c0b582aa434fb09b2d86897fa2b419ffeccf934b36f87fcedc8e835c2" +checksum = "ef7dd7b09e71aac9271c60031d0e558966cdb3253ba0308ab369bb2de80630d0" dependencies = [ "either", "fnv", @@ -3198,7 +3206,7 @@ dependencies = [ "libp2p-identity", "log", "multiaddr", - "multihash 0.17.0", + "multihash 0.19.0", "multistream-select", "once_cell", "parking_lot 0.12.1", @@ -3214,27 +3222,26 @@ dependencies = [ [[package]] name = "libp2p-identity" -version = "0.1.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2d584751cecb2aabaa56106be6be91338a60a0f4e420cf2af639204f596fc1" +checksum = "d2874d9c6575f1d7a151022af5c42bb0ffdcdfbafe0a6fd039de870b384835a2" dependencies = [ - "bs58", + "bs58 0.5.0", "ed25519-dalek", "log", - "multiaddr", - "multihash 0.17.0", + "multihash 0.19.0", "quick-protobuf", "rand 0.8.5", - "sha2 0.10.6", + "sha2 0.10.7", "thiserror", "zeroize", ] [[package]] name = "libp2p-swarm" -version = "0.42.2" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903b3d592d7694e56204d211f29d31bc004be99386644ba8731fc3e3ef27b296" +checksum = "a6f1fe3817492f88c5298c8b5fbaa5ff3a0c802ecf4e79be4e341cf07abfa82f" dependencies = [ "either", "fnv", @@ -3244,6 +3251,8 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", + "multistream-select", + "once_cell", "rand 0.8.5", "smallvec", "void", @@ -3364,7 +3373,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3434,16 +3443,16 @@ dependencies = [ [[package]] name = "multiaddr" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b36f567c7099511fa8612bbbb52dda2419ce0bdbacf31714e3a5ffdb766d3bd" +checksum = "92a651988b3ed3ad1bc8c87d016bb92f6f395b84ed1db9b926b32b1fc5a2c8b5" dependencies = [ "arrayref", "byteorder", "data-encoding", - "log", + "libp2p-identity", "multibase 0.9.1", - "multihash 0.17.0", + "multihash 0.19.0", "percent-encoding", "serde", "static_assertions", @@ -3483,23 +3492,22 @@ dependencies = [ "blake2s_simd", "blake3", "core2", - "digest 0.10.6", + "digest 0.10.7", "multihash-derive", "serde", "serde-big-array", - "sha2 0.10.6", + "sha2 0.10.7", "sha3 0.10.7", "unsigned-varint", ] [[package]] name = "multihash" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" +checksum = "2fd59dcc2bbe70baabeac52cd22ae52c55eefe6c38ff11a9439f16a350a939f2" dependencies = [ "core2", - "multihash-derive", "unsigned-varint", ] @@ -3519,9 +3527,9 @@ dependencies = [ [[package]] name = "multistream-select" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8552ab875c1313b97b8d20cb857b9fd63e2d1d6a0a1b53ce9821e575405f27a" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" dependencies = [ "bytes", "futures", @@ -3662,9 +3670,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -3840,7 +3848,7 @@ checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ "ecdsa", "elliptic-curve", - "sha2 0.10.6", + "sha2 0.10.7", ] [[package]] @@ -4007,22 +4015,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "6e138fdd8263907a2b0e1b4e80b7e58c721126479b6e6eedfb1b402acea7b9bd" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "d1fef411b303e3e12d534fb6e7852de82da56edd937d895125821fb7c09436c7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -4161,9 +4169,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" dependencies = [ "unicode-ident", ] @@ -4629,7 +4637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cf22754c49613d2b3b119f0e5d46e34a2c628a937e3024b8762de4e7d8c710b" dependencies = [ "byteorder", - "digest 0.10.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-iter", @@ -4751,9 +4759,9 @@ checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "rw-stream-sink" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" dependencies = [ "futures", "pin-project", @@ -5181,7 +5189,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -5211,13 +5219,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -5238,7 +5246,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "keccak", ] @@ -5276,7 +5284,7 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -5433,7 +5441,7 @@ dependencies = [ "bytes", "chrono", "crossbeam-queue", - "digest 0.10.6", + "digest 0.10.7", "dirs", "dotenvy", "either", @@ -5468,7 +5476,7 @@ dependencies = [ "serde", "serde_json", "sha1", - "sha2 0.10.6", + "sha2 0.10.7", "smallvec", "sqlformat", "sqlx-rt", @@ -5553,7 +5561,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2c479690955bebece0279a5b1ab9d7d584402caed9f56ecec346d0bc63661f" dependencies = [ - "bs58", + "bs58 0.4.0", "ssi-jwk", "thiserror", ] @@ -5581,12 +5589,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f41a12b15af9dce950a24a3295a2540be3b8500467621e31a97ddbe7618a5c8" dependencies = [ - "bs58", + "bs58 0.4.0", "digest 0.9.0", "k256", "keccak-hash", "ripemd160", - "sha2 0.10.6", + "sha2 0.10.7", "thiserror", "zeroize", ] @@ -5599,7 +5607,7 @@ checksum = "62e3c375b0fb2129c691e65e776c9105290ade34b56f39755f4f9c40ba98e41c" dependencies = [ "anyhow", "async-trait", - "bs58", + "bs58 0.4.0", "chrono", "derive_builder", "hex", @@ -5648,7 +5656,7 @@ checksum = "fbaa04abdfe9de454fe34c0c49a4920a1c595c4ef12d357d17ce94a8a8da0910" dependencies = [ "base64 0.12.3", "blake2b_simd 0.5.11", - "bs58", + "bs58 0.4.0", "ed25519-dalek", "getrandom 0.2.9", "k256", @@ -5681,7 +5689,7 @@ dependencies = [ "rsa", "serde", "serde_json", - "sha2 0.10.6", + "sha2 0.10.7", "ssi-crypto", "ssi-jwk", "thiserror", @@ -5709,7 +5717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de930bb18e3ed3c1f7b0a2b2b4fdba2887dffff34bb5f44b9967a983fea2d60c" dependencies = [ "async-trait", - "bs58", + "bs58 0.4.0", "chrono", "grdf", "hex", @@ -5755,7 +5763,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b57d919e20d214253a9a8dbc5f3b08ff555364934d99a09c828becab27a823" dependencies = [ - "bs58", + "bs58 0.4.0", "ed25519-dalek", "ssi-jwk", "ssi-jws", diff --git a/Cargo.toml b/Cargo.toml index a66fef0a..726fed92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ base64 = "0.13" futures = { default-features = false, version = "0.3", features = ["alloc", "std"] } hyper = "0.14" # Prometheus server lazy_static = "1.4.0" -libp2p = { default-features = false, version = "0.51.3" } opentelemetry = { version = "0.17.0", features = ["rt-tokio"] } opentelemetry-jaeger = { version = "0.16.0", features = ["rt-tokio", "reqwest_collector_client"] } pin-project = "1" diff --git a/README.md b/README.md index eb091768..705718b2 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ The following common options are available: | storage.blocks.type | KEPLER_STORAGE_BLOCKS_TYPE | Set the mode of block storage, options are "Local" and "S3" | | storage.limit | KEPLER_STORAGE_LIMIT | Set a maximum limit on storage available to Orbits hosted on this instance. Limits are written as strings, e.g. `10 MiB`, `100 GiB` | | storage.database | KEPLER_STORAGE_DATABASE | Set the location of the SQL database | -| storage.staging | KEPLER_STORAGE_STAGING | Set the mode of content staging, options are "Memory" and "FileSystem" | +| storage.staging | KEPLER_STORAGE_STAGING | Set the mode of content staging, options are "Memory" and "FileSystem" | +| keys.type | KEPLER_KEYS_TYPE | Set the type of host key store, options are "Static" | | orbits.allowlist | KEPLER_ORBITS_ALLOWLIST | Set the URL of an allowlist service for gating the creation of Orbit Peers | ### Database Config @@ -95,6 +96,20 @@ When `storage.blocks.type` is `S3` the instance will use the S3 AWS service for Additionally, the following environment variables must be present: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_DEFAULT_REGION`. +### Keys Config + +Kepler hosts require key pairs to provide replication. The `keys` config fields specify how a Kepler instance generates and stores these key pairs. + +#### Static Secret Derivation + +When `keys.type` is `Static` the instance will use an array of bytes as a static secret from which it will derive key pairs on a per-Orbit basis. The following config options will be available: + +| Option | env var | description | +|:------------|:-------------------|:-----------------------------------------------------------------------------| +| keys.secret | KEPLER_KEYS_SECRET | Unpadded base64Url-encoded byte string from which key pairs will be derived. | + +The secret MUST contain at least 32 bytes of entropy (either randomly generated or derived in a cryptographically secure way). It is STRONGLY RECOMMENDED that the secret be given via environment variables and NOT in the `kepler.toml` config file. Additionally it is STRONGLY RECOMMENDED that the secret be backed up in a secure place if used in production. Loss of the secret will result in total loss of function for the Kepler instance. + ## Running Kepler instances can be started via command line, e.g.: diff --git a/kepler-core/Cargo.toml b/kepler-core/Cargo.toml index 97e0d9e0..190bf52e 100644 --- a/kepler-core/Cargo.toml +++ b/kepler-core/Cargo.toml @@ -21,7 +21,7 @@ futures = { default-features = false, version = "0.3", features = ["alloc", "std pin-project = "1" time = "0.3" kepler-lib = { version = "0.1", path = "../lib" } -libp2p = { version = "0.51.3", default-features = false } +libp2p = { version = "0.52.1", default-features = false, features = ["ed25519"] } thiserror = "1" ssi = "0.6" serde = { version = "1", features = ["derive"] } diff --git a/kepler-core/src/db.rs b/kepler-core/src/db.rs index 7b0533ed..c8105331 100644 --- a/kepler-core/src/db.rs +++ b/kepler-core/src/db.rs @@ -1,5 +1,6 @@ use crate::events::{epoch_hash, Delegation, Event, HashError, Invocation, Operation, Revocation}; use crate::hash::Hash; +use crate::keys::Secrets; use crate::migrations::Migrator; use crate::models::*; use crate::relationships::*; @@ -13,6 +14,7 @@ use kepler_lib::{ authorization::{EncodingError, KeplerDelegation}, resource::OrbitId, }; +use libp2p::identity::PublicKey; use sea_orm::{ entity::prelude::*, error::{DbErr, RuntimeErr, SqlxError}, @@ -24,9 +26,10 @@ use sea_orm_migration::MigratorTrait; use std::collections::HashMap; #[derive(Debug, Clone)] -pub struct OrbitDatabase { +pub struct OrbitDatabase { conn: C, storage: B, + secrets: S, } #[derive(Debug, Clone)] @@ -39,7 +42,7 @@ pub struct Commit { #[non_exhaustive] #[derive(Debug, thiserror::Error)] -pub enum TxError { +pub enum TxError { #[error("database error: {0}")] Db(#[from] DbErr), #[error(transparent)] @@ -58,20 +61,23 @@ pub enum TxError { Encoding(#[from] EncodingError), #[error(transparent)] StoreSetup(S::Error), + #[error(transparent)] + Secrets(K::Error), #[error("Orbit not found")] OrbitNotFound, } #[non_exhaustive] #[derive(Debug, thiserror::Error)] -pub enum TxStoreError +pub enum TxStoreError where B: ImmutableReadStore + ImmutableWriteStore + ImmutableDeleteStore + StorageSetup, S: ImmutableStaging, S::Writable: 'static + Unpin, + K: Secrets, { #[error(transparent)] - Tx(#[from] TxError), + Tx(#[from] TxError), #[error(transparent)] StoreRead(::Error), #[error(transparent)] @@ -84,25 +90,51 @@ where MissingInput, } -impl From for TxStoreError +impl From for TxStoreError where B: ImmutableReadStore + ImmutableWriteStore + ImmutableDeleteStore + StorageSetup, S: ImmutableStaging, S::Writable: 'static + Unpin, + K: Secrets, { fn from(e: DbErr) -> Self { TxStoreError::Tx(e.into()) } } -impl OrbitDatabase { - pub async fn wrap(conn: DatabaseConnection, storage: B) -> Result { +impl OrbitDatabase { + pub async fn new(conn: DatabaseConnection, storage: B, secrets: K) -> Result { Migrator::up(&conn, None).await?; - Ok(Self { conn, storage }) + Ok(Self { + conn, + storage, + secrets, + }) + } +} + +impl OrbitDatabase +where + K: Secrets, +{ + pub async fn stage_key(&self, orbit: &OrbitId) -> Result { + self.secrets.stage_keypair(orbit).await } } -impl OrbitDatabase +impl OrbitDatabase +where + C: TransactionTrait, +{ + // to allow users to make custom read queries + pub async fn readable(&self) -> Result { + self.conn + .begin_with_config(None, Some(sea_orm::AccessMode::ReadOnly)) + .await + } +} + +impl OrbitDatabase where B: StoreSize, { @@ -113,18 +145,22 @@ where pub type InvocationInputs = HashMap<(OrbitId, String), (Metadata, HashBuffer)>; -impl OrbitDatabase +impl OrbitDatabase where C: TransactionTrait, B: StorageSetup, + K: Secrets, { - async fn transact(&self, events: Vec) -> Result, TxError> { + async fn transact( + &self, + events: Vec, + ) -> Result, TxError> { let tx = self .conn .begin_with_config(Some(sea_orm::IsolationLevel::ReadUncommitted), None) .await?; - let commit = transact(&tx, &self.storage, events).await?; + let commit = transact(&tx, &self.storage, &self.secrets, events).await?; tx.commit().await?; @@ -134,7 +170,7 @@ where pub async fn delegate( &self, delegation: Delegation, - ) -> Result, TxError> { + ) -> Result, TxError> { self.transact(vec![Event::Delegation(Box::new(delegation))]) .await } @@ -142,18 +178,11 @@ where pub async fn revoke( &self, revocation: Revocation, - ) -> Result, TxError> { + ) -> Result, TxError> { self.transact(vec![Event::Revocation(Box::new(revocation))]) .await } - // to allow users to make custom read queries - pub async fn readable(&self) -> Result { - self.conn - .begin_with_config(None, Some(sea_orm::AccessMode::ReadOnly)) - .await - } - pub async fn invoke( &self, invocation: Invocation, @@ -163,7 +192,7 @@ where HashMap, Vec>, ), - TxStoreError, + TxStoreError, > where B: ImmutableWriteStore + ImmutableDeleteStore + ImmutableReadStore, @@ -219,6 +248,7 @@ where let commit = transact( &tx, &self.storage, + &self.secrets, vec![Event::Invocation(Box::new(invocation), ops)], ) .await?; @@ -288,7 +318,7 @@ pub enum InvocationOutcome { OpenSessions(HashMap), } -impl From for TxError { +impl From for TxError { fn from(e: delegation::Error) -> Self { match e { delegation::Error::InvalidDelegation(e) => Self::InvalidDelegation(e), @@ -297,7 +327,7 @@ impl From for TxError { } } -impl From for TxError { +impl From for TxError { fn from(e: invocation::Error) -> Self { match e { invocation::Error::InvalidInvocation(e) => Self::InvalidInvocation(e), @@ -306,7 +336,7 @@ impl From for TxError { } } -impl From for TxError { +impl From for TxError { fn from(e: revocation::Error) -> Self { match e { revocation::Error::InvalidRevocation(e) => Self::InvalidRevocation(e), @@ -366,11 +396,12 @@ async fn event_orbits<'a, C: ConnectionTrait>( Ok(orbits) } -pub(crate) async fn transact( +pub(crate) async fn transact( db: &C, store_setup: &S, + secrets: &K, events: Vec, -) -> Result, TxError> { +) -> Result, TxError> { // for each event, get the hash and the relevent orbit(s) let event_hashes = events .into_iter() @@ -576,6 +607,10 @@ pub(crate) async fn transact( .create(&orbit.0) .await .map_err(TxError::StoreSetup)?; + secrets + .save_keypair(&orbit.0) + .await + .map_err(TxError::Secrets)?; } Ok(orbit_order @@ -688,10 +723,10 @@ async fn get_kv_entity( // }) } -async fn get_valid_delegations( +async fn get_valid_delegations( db: &C, orbit: &OrbitId, -) -> Result, TxError> { +) -> Result, TxError> { let (dels, abilities): (Vec, Vec>) = delegation::Entity::find() .left_join(revocation::Entity) diff --git a/kepler-core/src/keys.rs b/kepler-core/src/keys.rs new file mode 100644 index 00000000..31697e3a --- /dev/null +++ b/kepler-core/src/keys.rs @@ -0,0 +1,78 @@ +use kepler_lib::{ + libipld::cid::multihash::{Blake3_256, Hasher}, + resource::OrbitId, +}; +use libp2p::{ + identity::{ + ed25519::{Keypair as EdKP, SecretKey}, + DecodingError, Keypair, PublicKey, + }, + PeerId, +}; +use sea_orm_migration::async_trait::async_trait; +use std::error::Error as StdError; + +#[async_trait] +pub trait Secrets { + type Error: StdError; + async fn get_keypair(&self, orbit: &OrbitId) -> Result; + async fn get_pubkey(&self, orbit: &OrbitId) -> Result { + Ok(self.get_keypair(orbit).await?.public()) + } + async fn stage_keypair(&self, orbit: &OrbitId) -> Result; + async fn save_keypair(&self, orbit: &OrbitId) -> Result<(), Self::Error>; + async fn get_peer_id(&self, orbit: &OrbitId) -> Result { + Ok(self.get_pubkey(orbit).await?.to_peer_id()) + } +} + +#[async_trait] +pub trait SecretsSetup { + type Error: StdError; + type Input; + type Output: Secrets; + async fn setup(&self, input: Self::Input) -> Result; +} + +#[derive(Clone)] +pub struct StaticSecret { + secret: Vec, +} + +impl StaticSecret { + pub fn new(secret: Vec) -> Result> { + if secret.len() < 32 { + Err(secret) + } else { + Ok(Self { secret }) + } + } +} + +#[async_trait] +impl Secrets for StaticSecret { + type Error = DecodingError; + async fn get_keypair(&self, orbit: &OrbitId) -> Result { + let mut hasher = Blake3_256::default(); + hasher.update(&self.secret); + hasher.update(orbit.to_string().as_bytes()); + let derived = hasher.finalize().to_vec(); + Ok(EdKP::from(SecretKey::try_from_bytes(derived)?).into()) + } + async fn stage_keypair(&self, orbit: &OrbitId) -> Result { + self.get_pubkey(orbit).await + } + async fn save_keypair(&self, _orbit: &OrbitId) -> Result<(), Self::Error> { + Ok(()) + } +} + +#[async_trait] +impl SecretsSetup for StaticSecret { + type Error = std::convert::Infallible; + type Input = (); + type Output = Self; + async fn setup(&self, _input: Self::Input) -> Result { + Ok(self.clone()) + } +} diff --git a/kepler-core/src/lib.rs b/kepler-core/src/lib.rs index 4953f365..a6b29946 100644 --- a/kepler-core/src/lib.rs +++ b/kepler-core/src/lib.rs @@ -1,6 +1,7 @@ pub mod db; pub mod events; pub mod hash; +pub mod keys; pub mod manifest; pub mod migrations; pub mod models; @@ -10,5 +11,6 @@ pub mod types; pub mod util; pub use db::{Commit, InvocationOutcome, OrbitDatabase, TxError, TxStoreError}; +pub use libp2p; pub use sea_orm; pub use sea_orm_migration; diff --git a/kepler.toml b/kepler.toml index 27b1c364..f096e27b 100644 --- a/kepler.toml +++ b/kepler.toml @@ -20,6 +20,10 @@ # type = "Local" # path = "./kepler/blocks" +[global.keys] + type = "Static" + secret = "U29tZSBsb25nIHBpZWNlIG9mIGVudHJvcHkgd2hpY2ggaXMgYSBzZWNyZXQgYW5kIG1vcmUgdGhhbiAzMiBieXRlcw" + [global.orbits] ## Orbit allow list api endpoint # allowlist = "http://localhost:10000" diff --git a/src/auth_guards.rs b/src/auth_guards.rs index e188e366..74e05db8 100644 --- a/src/auth_guards.rs +++ b/src/auth_guards.rs @@ -7,7 +7,7 @@ use kepler_core::{ use kepler_lib::{ authorization::{EncodingError, HeaderEncode}, libipld::cid::Cid, - resource::{OrbitId, ResourceId}, + resource::OrbitId, }; use rocket::{ data::{Capped, FromData}, @@ -21,60 +21,9 @@ use rocket::{ }; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap}; -use thiserror::Error; use tokio_util::compat::FuturesAsyncReadCompatExt; use tracing::{info_span, Instrument}; -pub fn simple_check(target: &ResourceId, capability: &ResourceId) -> Result<()> { - check_orbit_and_service(target, capability)?; - simple_prefix_check(target, capability)?; - simple_check_fragments(target, capability) -} - -pub fn simple_check_fragments(target: &ResourceId, capability: &ResourceId) -> Result<()> { - match (target.fragment(), capability.fragment()) { - (Some(t), Some(c)) if t == c => Ok(()), - _ => Err(anyhow!("Target Action does not match Capability")), - } -} - -pub fn simple_prefix_check(target: &ResourceId, capability: &ResourceId) -> Result<()> { - // if #action is same - // Ok if target.path => cap.path - if target.service() == capability.service() - && match (target.path(), capability.path()) { - (Some(t), Some(c)) => t.starts_with(c), - (_, None) => true, - _ => false, - } - { - Ok(()) - } else { - Err(anyhow!("Target Service and Path are not correct")) - } -} - -#[derive(Error, Debug)] -pub enum TargetCheckError { - #[error("Invocation and Capability Orbits do not match")] - IncorrectOrbit, - #[error("Invocation and Capability Services do not match")] - IncorrectService, -} - -pub fn check_orbit_and_service( - target: &ResourceId, - capability: &ResourceId, -) -> Result<(), TargetCheckError> { - if target.orbit() != capability.orbit() { - Err(TargetCheckError::IncorrectOrbit) - } else if target.service() != capability.service() { - Err(TargetCheckError::IncorrectService) - } else { - Ok(()) - } -} - #[derive(Debug)] pub enum DataHolder { None, diff --git a/src/config.rs b/src/config.rs index ae6dbda3..64f7997a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,9 +3,14 @@ use crate::{ storage::{file_system::FileSystemConfig, s3::S3BlockConfig}, BlockConfig, BlockStage, }; +use kepler_core::keys::StaticSecret; use rocket::data::ByteUnit; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, FromInto}; +use serde_with::{ + base64::{Base64, UrlSafe}, + formats::Unpadded, + serde_as, FromInto, +}; #[derive(Serialize, Deserialize, Debug, Default, Clone, Hash, PartialEq, Eq)] pub struct Config { @@ -15,6 +20,42 @@ pub struct Config { pub relay: Relay, pub prometheus: Prometheus, pub cors: bool, + pub keys: Keys, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)] +#[serde(tag = "type")] +pub enum Keys { + Static(Static), +} + +impl Default for Keys { + fn default() -> Self { + Self::Static(Static::default()) + } +} + +#[serde_as] +#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq, Default)] +pub struct Static { + #[serde_as(as = "Option>")] + secret: Option>, +} + +#[derive(Debug, thiserror::Error)] +pub enum SecretInitError { + #[error("Secret required to be at least 32 bytes, but was {0}")] + NotEnoughEntropy(usize), + #[error("Missing secret")] + MissingSecret, +} + +impl TryFrom for StaticSecret { + type Error = SecretInitError; + fn try_from(s: Static) -> Result { + let secret = s.secret.ok_or(SecretInitError::MissingSecret)?; + StaticSecret::new(secret).map_err(|v| SecretInitError::NotEnoughEntropy(v.len())) + } } #[derive(Serialize, Deserialize, Debug, Default, Clone, Hash, PartialEq, Eq)] diff --git a/src/lib.rs b/src/lib.rs index de5cc777..884729e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,15 +19,14 @@ pub mod routes; pub mod storage; mod tracing; -use config::{BlockStorage, Config, StagingStorage}; +use config::{BlockStorage, Config, Keys, StagingStorage}; use kepler_core::{ + keys::{SecretsSetup, StaticSecret}, sea_orm::{Database, DatabaseConnection}, storage::{either::Either, memory::MemoryStaging, StorageConfig}, OrbitDatabase, }; -use libp2p::{identity::Keypair, PeerId}; use routes::{delegate, invoke, open_host_key, util_routes::*}; -use std::{collections::HashMap, sync::RwLock}; use storage::{ file_system::{FileSystemConfig, FileSystemStore, TempFileSystemStage}, s3::{S3BlockConfig, S3BlockStore}, @@ -74,7 +73,7 @@ impl From for StagingStorage { } } -pub type Kepler = OrbitDatabase; +pub type Kepler = OrbitDatabase; pub async fn app(config: &Figment) -> Result> { let kepler_config: Config = config.extract::()?; @@ -83,9 +82,14 @@ pub async fn app(config: &Figment) -> Result> { let routes = routes![healthcheck, cors, open_host_key, invoke, delegate,]; - let kepler = Kepler::wrap( + let key_setup: StaticSecret = match kepler_config.keys { + Keys::Static(s) => s.try_into()?, + }; + + let kepler = Kepler::new( Database::connect(&kepler_config.storage.database).await?, kepler_config.storage.blocks.open().await?, + key_setup.setup(()).await?, ) .await?; @@ -96,8 +100,7 @@ pub async fn app(config: &Figment) -> Result> { header_name: kepler_config.log.tracing.traceheader, }) .manage(kepler) - .manage(kepler_config.storage.staging.open().await?) - .manage(RwLock::new(HashMap::::new())); + .manage(kepler_config.storage.staging.open().await?); if kepler_config.cors { Ok(rocket.attach(AdHoc::on_response("CORS", |_, resp| { diff --git a/src/routes/mod.rs b/src/routes/mod.rs index a696a3a4..53d46743 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,7 +1,6 @@ use anyhow::Result; -use libp2p::identity::{Keypair, PeerId}; use rocket::{data::ToByteUnit, http::Status, State}; -use std::{collections::HashMap, sync::RwLock}; +use std::collections::HashMap; use tokio_util::compat::TokioAsyncReadCompatExt; use tracing::{info_span, Instrument}; @@ -31,16 +30,25 @@ pub mod util_routes { pub fn healthcheck() {} } -#[get("/peer/generate")] -pub fn open_host_key( - s: &State>>, +#[get("/peer/generate/")] +pub async fn open_host_key( + s: &State, + orbit: &str, ) -> Result { - let keypair = Keypair::generate_ed25519(); - let id = keypair.public().to_peer_id(); - s.write() - .map_err(|_| (Status::InternalServerError, "cant read keys"))? - .insert(id, keypair); - Ok(id.to_base58()) + Ok(s.stage_key( + &orbit + .parse() + .map_err(|_| (Status::BadRequest, "Invalid orbit ID"))?, + ) + .await + .map_err(|_| { + ( + Status::InternalServerError, + "Failed to stage keypair for orbit", + ) + })? + .to_peer_id() + .to_base58()) } #[post("/delegate")]