From 5d739899d361cead3bbe89dd05e0e87c38e8f5ec Mon Sep 17 00:00:00 2001 From: Billy Batista Date: Sun, 21 Apr 2024 15:21:57 -0400 Subject: [PATCH] fly.io deployments, generate TLS certs (in prod), less deps --- Cargo.lock | 465 +++++++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 12 +- Dockerfile | 9 +- flake.nix | 22 ++- fly.toml | 12 +- src/db.rs | 50 +++--- src/main.rs | 116 ++++++++++--- src/tls.rs | 180 ++++++++++++++++++++ 8 files changed, 753 insertions(+), 113 deletions(-) create mode 100644 src/tls.rs diff --git a/Cargo.lock b/Cargo.lock index 293f9a5..9568059 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,6 +61,18 @@ version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -97,7 +109,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", "synstructure", ] @@ -109,18 +121,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", -] - -[[package]] -name = "async-trait" -version = "0.1.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -180,9 +181,10 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bfsp" version = "0.1.0" -source = "git+https://github.com/Billy-s-E2EE-File-Server/bfsp.git#df920b35cf046ebea8b124d38b932d96d59586a3" +source = "git+https://github.com/Billy-s-E2EE-File-Server/bfsp.git#49d72a221af6f11481f7f1092214e6fcda9481e1" dependencies = [ "anyhow", + "argon2", "base64 0.22.0", "bincode", "blake3", @@ -285,6 +287,15 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "blake3" version = "1.5.1" @@ -337,12 +348,13 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -519,7 +531,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -624,7 +636,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -667,6 +679,15 @@ dependencies = [ "serde", ] +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -727,18 +748,20 @@ name = "file_server" version = "0.1.0" dependencies = [ "anyhow", - "async-trait", "bfsp", "biscuit-auth", "fern", "futures", "humantime", + "instant-acme", "log", "rand", - "serde", + "rcgen", + "serde_json", "sqlx", "thiserror", "tokio", + "warp", "wtransport", ] @@ -774,6 +797,12 @@ dependencies = [ "spin 0.9.8", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -850,7 +879,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -923,6 +952,25 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "halfbrown" version = "0.2.5" @@ -952,6 +1000,30 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + [[package]] name = "heck" version = "0.4.1" @@ -1012,12 +1084,96 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + [[package]] name = "idna" version = "0.5.0" @@ -1047,6 +1203,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant-acme" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e78737dbac1bae14cb5556c9cd7c604886095c59cdb5af71f12a4c59be2b05" +dependencies = [ + "base64 0.21.7", + "hyper", + "hyper-rustls", + "ring 0.17.8", + "rustls-pki-types", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -1244,6 +1416,22 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1270,6 +1458,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 0.2.12", + "httparse", + "log", + "memchr", + "mime", + "spin 0.9.8", + "version_check", +] + [[package]] name = "multimap" version = "0.10.0" @@ -1431,6 +1637,17 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.14" @@ -1472,6 +1689,26 @@ dependencies = [ "indexmap", ] +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1547,7 +1784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" dependencies = [ "proc-macro2", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -1620,7 +1857,7 @@ dependencies = [ "prost 0.12.4", "prost-types 0.12.4", "regex", - "syn 2.0.59", + "syn 2.0.60", "tempfile", ] @@ -1647,7 +1884,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -1825,7 +2062,7 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -1939,9 +2176,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad" dependencies = [ "bitflags 2.5.0", "errno", @@ -1952,9 +2189,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", "ring 0.17.8", @@ -2035,6 +2272,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -2097,7 +2340,7 @@ checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -2120,6 +2363,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2478,9 +2733,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.59" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -2495,7 +2750,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -2512,22 +2767,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -2601,7 +2856,17 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", ] [[package]] @@ -2615,6 +2880,32 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.8.12" @@ -2638,9 +2929,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.9" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ "indexmap", "serde", @@ -2649,6 +2940,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" @@ -2669,7 +2966,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] @@ -2681,6 +2978,31 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -2697,6 +3019,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -2769,6 +3100,12 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "1.8.0" @@ -2802,6 +3139,44 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http 0.2.12", + "hyper", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -2841,7 +3216,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", "wasm-bindgen-shared", ] @@ -2863,7 +3238,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3143,7 +3518,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.60", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e1e4b2f..3bcd153 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [profile.release] -lto = "fat" -debug = true +# TODO re-enable this when we don't do 50 deploys a day +#lto = "fat" [dependencies] anyhow = { version = "1" } @@ -23,8 +23,12 @@ rand = "0.8" wtransport = { version = "0.1" } #bfsp = { path = "../bfsp" } bfsp = { git = "https://github.com/Billy-s-E2EE-File-Server/bfsp.git" } -async-trait = { version = "0.1" } futures = { version = "0.3", features = ["executor"] } thiserror = "1" biscuit-auth = { version = "4" } -serde = { version = "1" } + +# TLS cert stuff +instant-acme = "0.4" +rcgen = "0.12.1" +warp = "0.3" +serde_json = "1" diff --git a/Dockerfile b/Dockerfile index b078088..ac35847 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,5 @@ FROM nixos/nix:latest as builder -COPY src /tmp/build/src COPY flake.nix flake.lock /tmp/build/ COPY rust-toolchain.toml /tmp/build/ COPY Cargo.toml Cargo.lock /tmp/build/ @@ -8,13 +7,19 @@ COPY migrations /tmp/build/migrations WORKDIR /tmp/build +RUN nix --extra-experimental-features "nix-command flakes" --option filter-syscalls false build '.#build-deps' + +COPY src /tmp/build/src + RUN nix --extra-experimental-features "nix-command flakes" --option filter-syscalls false build '.?submodules=1' RUN mkdir -p /tmp/nix-store-closure RUN cp -R $(nix-store -qR result/) /tmp/nix-store-closure -FROM scratch +FROM debian:bookworm-slim WORKDIR /app +RUN apt-get update && apt-get install ca-certificates -y +RUN update-ca-certificates -f COPY --from=builder /tmp/nix-store-closure /nix/store COPY --from=builder /tmp/build/result /app COPY migrations /app/migrations diff --git a/flake.nix b/flake.nix index 97e4113..51e3b14 100644 --- a/flake.nix +++ b/flake.nix @@ -23,18 +23,26 @@ rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; craneLib = crane.lib.${system}.overrideToolchain rustToolchain; + + src = craneLib.cleanCargoSource (craneLib.path ./.); + buildInputs = with pkgs; + [ clang_15 libsodium protobuf ] + ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ pkgs.libiconv ]; + cargoArtifacts = craneLib.buildDepsOnly { + inherit src; + buildInputs = buildInputs; + }; + my-crate = craneLib.buildPackage { - src = craneLib.cleanCargoSource (craneLib.path ./.); + inherit cargoArtifacts src; cargoVendorDir = craneLib.vendorCargoDeps { cargoLock = ./Cargo.lock; }; - buildInputs = with pkgs; - [ clang_15 libsodium protobuf ] - ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ pkgs.libiconv ]; - + buildInputs = buildInputs; }; in { packages.default = my-crate; + packages.build-deps = cargoArtifacts; devShells.default = craneLib.devShell { # Automatically inherit any build inputs from `my-crate` @@ -49,8 +57,8 @@ protolint sqlx-cli rust-analyzer - openssl - xxd + openssl + xxd ]; }; }); diff --git a/fly.toml b/fly.toml index 5d9e3ee..9446b6b 100644 --- a/fly.toml +++ b/fly.toml @@ -6,16 +6,22 @@ app = 'encrypted-file-server' primary_region = 'ord' -[build] - [[services]] + protocol = "udp" internal_port = 9999 auto_stop_machines = true auto_start_machines = true - min_machines_running = 0 + min_machines_running = 1 processes = ['app'] + [[services.ports]] + port = 9999 + +[http_service] + internal_port = 80 + force_https = false [env] + RUST_BACKTRACE="1" TOKEN_PUBLIC_KEY="d27fb9c11d7608f86aa9e90a00133d58688b2fe4e7903a35199a25f7e905f658" [[vm]] diff --git a/src/db.rs b/src/db.rs index e12b7d6..7cde277 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,51 +1,57 @@ use std::{ collections::{HashMap, HashSet}, env, + future::Future, }; use anyhow::Result; -use async_trait::async_trait; use bfsp::{ChunkHash, ChunkID, ChunkMetadata, EncryptedFileMetadata}; use log::debug; use sqlx::{Execute, PgPool, QueryBuilder, Row}; use thiserror::Error; -#[async_trait] -pub trait ChunkDatabase: Sized { +pub trait MetaDB: Sized { type InsertChunkError: std::error::Error; - async fn new() -> Result; - async fn contains_chunk(&self, chunk_id: ChunkID, user_id: i64) -> Result; - async fn insert_chunk( + fn new() -> impl Future> + Send; + fn contains_chunk( + &self, + chunk_id: ChunkID, + user_id: i64, + ) -> impl Future> + Send; + fn insert_chunk( &self, chunk_meta: ChunkMetadata, user_id: i64, - ) -> std::result::Result<(), InsertChunkError>; + ) -> impl Future> + Send; // TODO: add a funtion to get multiple chunsk - async fn get_chunk_meta( + fn get_chunk_meta( &self, chunk_id: ChunkID, user_id: i64, - ) -> Result>; - async fn delete_chunks(&self, chunk_ids: &HashSet) -> Result<()>; - async fn insert_file_meta( + ) -> impl Future>> + Send; + fn delete_chunks( + &self, + chunk_ids: &HashSet, + ) -> impl Future> + Send; + fn insert_file_meta( &self, enc_metadata: EncryptedFileMetadata, user_id: i64, - ) -> Result<()>; - async fn get_file_meta( + ) -> impl Future> + Send; + fn get_file_meta( &self, meta_id: i64, user_id: i64, - ) -> Result>; - async fn list_file_meta( + ) -> impl Future>> + Send; + fn list_file_meta( &self, meta_ids: HashSet, user_id: i64, - ) -> Result>; + ) -> impl Future>> + Send; } -pub struct PostgresDB { +pub struct PostgresMetaDB { pool: PgPool, } @@ -57,8 +63,7 @@ pub enum InsertChunkError { DatabaseError(#[from] sqlx::Error), } -#[async_trait] -impl ChunkDatabase for PostgresDB { +impl MetaDB for PostgresMetaDB { type InsertChunkError = InsertChunkError; async fn new() -> Result { @@ -68,12 +73,7 @@ impl ChunkDatabase for PostgresDB { ) .await?; - sqlx::migrate!() - .run(&pool) - .await - .map_err(|err| anyhow::anyhow!("Error running database migrations: {err:?}"))?; - - Ok(PostgresDB { pool }) + Ok(PostgresMetaDB { pool }) } async fn contains_chunk(&self, chunk_id: ChunkID, user_id: i64) -> Result { diff --git a/src/main.rs b/src/main.rs index defe03c..e56b08f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ // FIXME: there's some infinite loop that causes CPU usage to spike mod db; +mod tls; use anyhow::anyhow; use bfsp::list_file_metadata_resp::FileMetadatas; @@ -8,18 +9,21 @@ use biscuit_auth::PublicKey; use biscuit_auth::{macros::authorizer, Authorizer, Biscuit}; use std::env; use std::fmt::Display; +use std::net::ToSocketAddrs; use std::time::Duration; use std::{ collections::{HashMap, HashSet}, os::unix::prelude::MetadataExt, sync::Arc, }; +use tokio::io; use wtransport::endpoint::IncomingSession; use wtransport::Endpoint; use wtransport::Identity; use wtransport::ServerConfig; -use crate::db::{ChunkDatabase, InsertChunkError, PostgresDB}; +use crate::db::{InsertChunkError, MetaDB, PostgresMetaDB}; +use crate::tls::order_tls_certs; use anyhow::Result; use bfsp::{ chunks_uploaded_query_resp::{ChunkUploaded, ChunksUploaded}, @@ -59,7 +63,7 @@ async fn main() -> Result<()> { .level_for("quinn", log::LevelFilter::Warn) .level_for("rustls", log::LevelFilter::Warn) .level_for("tracing", log::LevelFilter::Warn) - .level_for("wtransport", log::LevelFilter::Warn) + .level_for("wtransport", log::LevelFilter::Trace) // - and per-module overrides // Output to stdout, files, and other Dispatch configurations .chain(std::io::stdout()) @@ -74,7 +78,7 @@ async fn main() -> Result<()> { info!("Initializing database"); let db = Arc::new( - PostgresDB::new() + PostgresMetaDB::new() .await .map_err(|err| anyhow!("Error initializing database: {err:?}")) .unwrap(), @@ -82,31 +86,68 @@ async fn main() -> Result<()> { info!("Starting server!"); - let config = ServerConfig::builder() - .with_bind_default(9999) - // TODO: this obviously won't work in prod - .with_identity( - &Identity::load_pemfiles("certs/localhost.pem", "certs/localhost-key.pem") - .await - .unwrap(), + let addr = match env::var("FLY_APP_NAME").is_ok() { + // in order to serve Webtransport (UDP) on Fly, we have to use fly-global-services, which keep in mind is IPV4 ONLY AS OF WRITING + true => "fly-global-services:9999" + .to_socket_addrs() + .unwrap() + .next() + .unwrap(), + // I <3 ipv6 + false => "[::]:9999".to_socket_addrs().unwrap().next().unwrap(), + }; + + if !cfg!(debug_assertions) && env::var("FLY_APP_NAME").is_ok() { + let cert_info = order_tls_certs().await?; + + fs::create_dir_all("/etc/letsencrypt/live/encrypted-file-server.fly.dev/").await?; + fs::write( + "/etc/letsencrypt/live/encrypted-file-server.fly.dev/chain.pem", + cert_info.cert_chain_pem, + ) + .await?; + fs::write( + "/etc/letsencrypt/live/encrypted-file-server.fly.dev/privkey.pem", + cert_info.private_key_pem, ) + .await?; + } + + let chain_file = match cfg!(debug_assertions) { + true => "certs/localhost.pem", + false => "/etc/letsencrypt/live/encrypted-file-server.fly.dev/chain.pem", + }; + let key_file = match cfg!(debug_assertions) { + true => "certs/localhost-key.pem", + false => "/etc/letsencrypt/live/encrypted-file-server.fly.dev/privkey.pem", + }; + + let config = ServerConfig::builder() + .with_bind_address(addr) + .with_identity(&Identity::load_pemfiles(chain_file, key_file).await.unwrap()) .keep_alive_interval(Some(Duration::from_secs(3))) + .allow_migration(true) + .max_idle_timeout(Some(Duration::from_secs(10))) + .unwrap() .build(); let server = Endpoint::server(config).unwrap(); + info!("Listening on {addr}"); + loop { let incoming_session = server.accept().await; let db = Arc::clone(&db); tokio::task::spawn(handle_connection(incoming_session, public_key, db)); + debug!("Spawned connection task") } } async fn handle_connection( incoming_session: IncomingSession, public_key: PublicKey, - db: Arc, + db: Arc, ) { info!("Transport connected"); @@ -116,20 +157,28 @@ async fn handle_connection( loop { let bi = conn.accept_bi().await; if let Err(err) = bi { - continue; + debug!("Error accepting connection: {err}"); + return; } + debug!("Accepted connection"); + + // A single socket can have multiple connections. Multiplexing! let (mut write_sock, mut read_sock) = bi.unwrap(); let db = Arc::clone(&db); + debug!("Bidirectionl connection established!"); + tokio::task::spawn(async move { loop { - // A single socket can have multiple connections. Multiplexing! - debug!("Bidirectionl connection established!"); - - trace!("Reading action length"); - let action_len = match read_sock.read_u32_le().await { + debug!("Waiting for message"); + let action_len = match read_sock.read_u32_le().await.map_err(|e| e.kind()) { Ok(len) => len, + Err(io::ErrorKind::UnexpectedEof) => { + debug!("Client disconnected"); + // This is fine, the client disconnected + return; + } Err(err) => { info!("Disconnecting from sock: {err}"); return; @@ -267,13 +316,14 @@ async fn handle_connection( debug!("Sending response of {} bytes", resp.len()); write_sock.write_all(&resp).await.unwrap(); write_sock.flush().await.unwrap(); + debug!("Sent response"); } }); } } -pub async fn handle_download_chunk( +pub async fn handle_download_chunk( chunk_db: &D, token: &Biscuit, chunk_id: ChunkID, @@ -286,10 +336,20 @@ pub async fn handle_download_chunk( deny if false; "# ); - debug!("Downloading chunk {}", chunk_id); - authorizer.add_token(token).unwrap(); - authorizer.authorize().unwrap(); + + let mut authorizer_clone = authorizer.clone(); + let authorize: anyhow::Result<()> = tokio::task::spawn_blocking(move || { + authorizer_clone.authorize().unwrap(); + + Ok(()) + }) + .await + .unwrap(); + + authorize.unwrap(); + + info!("Downloading chunk {}", chunk_id); let user_id = get_user_id(&mut authorizer).unwrap(); @@ -312,11 +372,13 @@ pub async fn handle_download_chunk( let mut chunk = Vec::with_capacity(chunk_file_metadata.size() as usize); chunk_file.read_to_end(&mut chunk).await?; + debug!("Sending chunk {chunk_id}"); + Ok(Some((chunk_meta, chunk))) } // FIXME: very ddosable by querying many chunks at once -async fn query_chunks_uploaded( +async fn query_chunks_uploaded( chunk_db: &D, token: &Biscuit, chunks: HashSet, @@ -348,7 +410,7 @@ async fn query_chunks_uploaded( } // TODO: Maybe store upload_chunk messages in files and mmap them? -async fn handle_upload_chunk( +async fn handle_upload_chunk( chunk_db: &D, token: &Biscuit, chunk_metadata: ChunkMetadata, @@ -411,7 +473,7 @@ async fn handle_upload_chunk( Ok(()) } -pub async fn handle_delete_chunks( +pub async fn handle_delete_chunks( chunk_db: &D, token: &Biscuit, chunk_ids: HashSet, @@ -452,7 +514,7 @@ pub enum UploadMetadataError { MultipleUserIDs, } -pub async fn handle_upload_file_metadata( +pub async fn handle_upload_file_metadata( chunk_db: &D, token: &Biscuit, enc_file_meta: EncryptedFileMetadata, @@ -482,7 +544,7 @@ pub async fn handle_upload_file_metadata( Ok(()) } -pub async fn handle_download_file_metadata( +pub async fn handle_download_file_metadata( chunk_db: &D, token: &Biscuit, meta_id: i64, @@ -506,7 +568,7 @@ pub async fn handle_download_file_metadata( } } -pub async fn handle_list_file_metadata( +pub async fn handle_list_file_metadata( chunk_db: &D, token: &Biscuit, meta_ids: Vec, diff --git a/src/tls.rs b/src/tls.rs new file mode 100644 index 0000000..bb18869 --- /dev/null +++ b/src/tls.rs @@ -0,0 +1,180 @@ +use std::sync::Arc; +use std::time::Duration; +use std::{env, sync::atomic::AtomicBool}; + +use log::{error, info}; +use rcgen::{Certificate, CertificateParams, DistinguishedName}; + +use instant_acme::{ + Account, AccountCredentials, AuthorizationStatus, ChallengeType, Identifier, NewOrder, + OrderState, OrderStatus, +}; +use tokio::sync::RwLock; + +struct AcmeChallenge { + pub token: String, + pub text: String, +} + +pub(crate) struct TlsCerts { + pub(crate) cert_chain_pem: String, + pub(crate) private_key_pem: String, +} + +pub async fn order_tls_certs() -> anyhow::Result { + let acme_challenge: Arc>> = Arc::new(RwLock::new(None)); + + let acme_challenge_clone = acme_challenge.clone(); + tokio::task::spawn(async move { + http_server(acme_challenge_clone).await; + }); + + let acc_key = env::var("LETS_ENCRYPT_KEY").unwrap(); + let acc_creds = serde_json::from_str::(acc_key.as_str()).unwrap(); + let account = Account::from_credentials(acc_creds).await.unwrap(); + + let identifier = Identifier::Dns("encrypted-file-server.fly.dev".to_string()); + let mut order = account + .new_order(&NewOrder { + identifiers: &[identifier], + }) + .await + .unwrap(); + + let state = order.state(); + + // Pick the desired challenge type and prepare the response. + let authorizations = order.authorizations().await.unwrap(); + let mut challenges = Vec::with_capacity(authorizations.len()); + for authz in &authorizations { + match authz.status { + AuthorizationStatus::Pending => {} + AuthorizationStatus::Valid => {} + _ => todo!(), + } + + let challenge = authz + .challenges + .iter() + .find(|c| c.r#type == ChallengeType::Http01) + .ok_or_else(|| anyhow::anyhow!("no http01 challenge found")) + .unwrap(); + + let Identifier::Dns(identifier) = &authz.identifier; + acme_challenge.write().await.replace(AcmeChallenge { + token: challenge.token.clone(), + text: order.key_authorization(challenge).as_str().to_string(), + }); + + challenges.push((identifier, &challenge.url)); + } + + // Let the server know we're ready to accept the challenges. + + for (_, url) in challenges.iter() { + order.set_challenge_ready(url).await.unwrap(); + } + + // Exponentially back off until the order becomes ready or invalid. + + let mut tries = 1u8; + let mut delay = Duration::from_millis(250); + let state = loop { + tokio::time::sleep(delay).await; + + order.refresh().await.unwrap(); + let state = order.state(); + if let OrderStatus::Ready | OrderStatus::Invalid | OrderStatus::Valid = state.status { + info!("order state: {:#?}", state); + break state; + } + + delay *= 2; + tries += 1; + match tries < 5 { + true => info!("order is not ready, waiting {delay:?} {state:?} {tries}"), + false => { + error!("order is not ready {state:?} {tries}"); + return Err(anyhow::anyhow!("order is not ready")); + } + } + }; + + if state.status == OrderStatus::Invalid { + return Err(anyhow::anyhow!("order is invalid")); + } + + let mut names = Vec::with_capacity(challenges.len()); + for (identifier, _) in challenges { + names.push(identifier.to_owned()); + } + + // If the order is ready, we can provision the certificate. + // Use the rcgen library to create a Certificate Signing Request. + + let mut params = CertificateParams::new(names.clone()); + params.distinguished_name = DistinguishedName::new(); + let cert = Certificate::from_params(params).unwrap(); + let csr = cert.serialize_request_der().unwrap(); + + // Finalize the order and print certificate chain, private key and account credentials. + + order.finalize(&csr).await.unwrap(); + tries = 0; + let cert_chain_pem = loop { + match order.certificate().await.unwrap() { + Some(cert_chain_pem) => break cert_chain_pem, + None => tokio::time::sleep(Duration::from_secs(1)).await, + } + tries += 1; + if tries > 10 { + panic!("No cert received"); + } + }; + + Ok(TlsCerts { + cert_chain_pem, + private_key_pem: cert.serialize_private_key_pem(), + }) +} + +static HTTP_SERVER_ACTIVE: AtomicBool = AtomicBool::new(false); + +async fn http_server(acme_challenge: Arc>>) { + if HTTP_SERVER_ACTIVE.load(std::sync::atomic::Ordering::SeqCst) { + return; + } + + HTTP_SERVER_ACTIVE.store(true, std::sync::atomic::Ordering::SeqCst); + + use warp::Filter; + let acme = + warp::path!(".well-known" / "acme-challenge" / String).then(move |challenge_token| { + let acme_challenge = acme_challenge.clone(); + async move { + info!("ACME challenge!"); + let challenge = acme_challenge.read().await; + match &*challenge { + Some(challenge) if challenge.token == challenge_token => { + let response = warp::reply::Response::new(challenge.text.clone().into()); + let (mut parts, body): (warp::http::response::Parts, _) = + response.into_parts(); + + parts.status = warp::http::status::StatusCode::OK; + warp::reply::Response::from_parts(parts, body) + } + _ => { + let response = + warp::reply::Response::new("404, stop visiting this page :)".into()); + let (mut parts, body): (warp::http::response::Parts, _) = + response.into_parts(); + + parts.status = warp::http::status::StatusCode::NOT_FOUND; + warp::reply::Response::from_parts(parts, body) + } + } + } + }); + + warp::serve(acme).run(([0, 0, 0, 0], 80)).await; +}