From 827c724c462271055517a2c87d7c5bac868bf8dd Mon Sep 17 00:00:00 2001 From: Joakim Hulthe Date: Fri, 16 Jul 2021 00:12:14 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 1 + Cargo.lock | 1222 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 17 + Dockerfile | 27 ++ README.md | 3 + src/cache.rs | 91 ++++ src/gamma.rs | 86 ++++ src/main.rs | 317 +++++++++++++ src/me.json | 225 ++++++++++ src/rules.rs | 90 ++++ 10 files changed, 2079 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 src/cache.rs create mode 100644 src/gamma.rs create mode 100644 src/main.rs create mode 100644 src/me.json create mode 100644 src/rules.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5c0775d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1222 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base-x" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bumpalo" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" + +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + +[[package]] +name = "cc" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compound-error" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "106f619aa16d817037b5820326364166e67c52d1a78eb6f37ba354ec82b617c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "const_fn" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7" + +[[package]] +name = "cookie" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3" +dependencies = [ + "cookie", + "idna", + "log", + "publicsuffix", + "serde", + "serde_json", + "time", + "url", +] + +[[package]] +name = "ctor" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + +[[package]] +name = "encoding_rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "femme" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af1a24f391a5a94d756db5092c6576aad494b88a71a5a36b20c67b63e0df034" +dependencies = [ + "cfg-if 0.1.10", + "js-sys", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[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.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" + +[[package]] +name = "futures-macro" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +dependencies = [ + "autocfg", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" + +[[package]] +name = "futures-task" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" + +[[package]] +name = "futures-util" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +dependencies = [ + "autocfg", + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "gamma_basic_auth_proxy" +version = "1.0.0" +dependencies = [ + "base64", + "compound-error", + "femme", + "hyper", + "kv-log-macro", + "log", + "reqwest", + "ron", + "serde", + "tokio", +] + +[[package]] +name = "h2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68" + +[[package]] +name = "httpdate" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" + +[[package]] +name = "hyper" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "futures-util", + "hyper", + "log", + "rustls", + "tokio", + "tokio-rustls", + "webpki", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + +[[package]] +name = "js-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5600b4e6efc5421841a2138a6b082e07fe12f9aaa12783d50e5d13325b26b4fc" + +[[package]] +name = "lock_api" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if 1.0.0", + "value-bag", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "mio" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" + +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "publicsuffix" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b4ce31ff0a27d93c8de1849cf58162283752f065a90d508f1105fa6c9a213f" +dependencies = [ + "idna", + "url", +] + +[[package]] +name = "quote" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22" +dependencies = [ + "base64", + "bytes", + "cookie", + "cookie_store", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "rustls", + "serde", + "serde_json", + "serde_urlencoded", + "time", + "tokio", + "tokio-rustls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "ron" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064ea8613fb712a19faf920022ec8ddf134984f100090764a4e1d768f3827f1f" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + +[[package]] +name = "socket2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check", +] + +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + +[[package]] +name = "syn" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check", + "winapi", +] + +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a38d31d7831c6ed7aad00aa4c12d9375fd225a6dd77da1d25b707346319a975" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "value-bag" +version = "1.0.0-alpha.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd320e1520f94261153e96f7534476ad869c14022aee1e59af7c778075d840ae" +dependencies = [ + "ctor", + "version_check", +] + +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +dependencies = [ + "cfg-if 1.0.0", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fba7978c679d53ce2d0ac80c8c175840feb849a161664365d1287b41f2e67f1" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" + +[[package]] +name = "web-sys" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e680928 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "gamma_basic_auth_proxy" +version = "1.0.0" +authors = ["Joakim Hulthe "] +edition = "2018" + +[dependencies] +hyper = { version = "0.14", features = ["full"] } +tokio = { version = "1", features = ["full"] } +serde = { version = "1", features = ["derive"] } +ron = "0.6.4" +compound-error = "0.1.2" +log = "0.4" +femme = "2" +kv-log-macro = "1" +base64 = "0.13" +reqwest = { version = "0.11.4", default-features = false, features = ["rustls-tls", "cookies", "multipart", "json"] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dc7b124 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +################### +### BUILD STAGE ### +################### +FROM rust:1.52 as build_stage + +# Install build dependencies +RUN rustup target add x86_64-unknown-linux-musl + +# Build project +WORKDIR /app +COPY . . +RUN cargo build --release --target x86_64-unknown-linux-musl +RUN strip target/x86_64-unknown-linux-musl/release/auth_proxy + +######################## +### PRODUCTION STAGE ### +######################## +FROM scratch + +EXPOSE 3000 + +WORKDIR / + +# Copy application binary +COPY --from=build_stage /app/target/x86_64-unknown-linux-musl/release/auth_proxy auth_proxy + +CMD ["/auth_proxy", "--help"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..35bc4b0 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +auth\_proxy +======== +_An HTTP based reverse proxy for adding basic auth to your stack_ diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 0000000..53278af --- /dev/null +++ b/src/cache.rs @@ -0,0 +1,91 @@ +use std::collections::HashMap; +use reqwest::Client; +use tokio::{sync::{RwLock, RwLockReadGuard, RwLockWriteGuard, Mutex}, time::{Duration, Instant}}; +use std::sync::Arc; + +use crate::{Opt, gamma::{self, Credentials, User}}; + +struct CacheEntry { + last_checked: Instant, + login_result: Result, + http_client: Client, +} +pub struct UserCache { + map: Mutex>>>>, +} + +// Arbitraty cache time, should be configurable +const CACHE_TIME: Duration = Duration::from_secs(10); + +impl UserCache { + pub(crate) fn new() -> Self { + UserCache { + map: Mutex::new(HashMap::new()), + } + } + + pub(crate) async fn login(&self, opt: &Opt, credentials: &Credentials) -> Result { + let mut map = self.map.lock().await; + + // if an entry already exists + if let Some(user_state) = map.get(credentials) { + // clone it + let user_state = Arc::clone(user_state); + drop(map); // release the map lock + + loop { + let entry = RwLockReadGuard::map(user_state.read().await, |option| option.as_ref().expect("already initialized")); + let last_checked = entry.last_checked; + // if the entry has not expired + if last_checked.elapsed() < CACHE_TIME { + // take the fast path and just assume we are still logged in + break entry.login_result.clone(); + } else { + // otherwise try to upgrade to a write lock so that we can refresh the session + drop(entry); + let mut entry = RwLockWriteGuard::map(user_state.write().await, |option| option.as_mut().expect("already initialized")); + + // if the entry was updated when we didn't have the lock, retry + if entry.last_checked != last_checked { + continue; + } + + let login_result = gamma::login(&mut entry.http_client, opt, credentials).await; + entry.last_checked = Instant::now(); + entry.login_result = login_result.clone(); + break login_result; + } + } + } else { + // if the entry did not exist, then we must log in + + // create an empty lock and take the write handle + let lock = Arc::new(RwLock::new(None)); + let mut entry = lock.write().await; + + // then put it in the map so that we can release the map as fast as possible + map.insert(credentials.clone(), Arc::clone(&lock)); + drop(map); // release the map lock + + let mut client = Client::builder() + .cookie_store(true) + .timeout(Duration::from_secs(10 /* TODO: configure timeout */)) + .build() + .expect("http client"); + + let login_result = gamma::login(&mut client, opt, credentials).await; + let last_checked = Instant::now(); + + // try to log in and cache the result + let new_entry = CacheEntry { + login_result: login_result.clone(), + last_checked, + http_client: client, + }; + + *entry = Some(new_entry); + + login_result + } + } +} \ No newline at end of file diff --git a/src/gamma.rs b/src/gamma.rs new file mode 100644 index 0000000..2c4ed40 --- /dev/null +++ b/src/gamma.rs @@ -0,0 +1,86 @@ +use kv_log_macro::debug; +use reqwest::{Client, Response}; +use serde::{Deserialize, Serialize}; + +use crate::Opt; + +#[derive(Clone, PartialEq, Eq, Hash, Serialize)] +pub struct Credentials { + pub username: String, + pub password: String, +} + +#[derive(Clone, Deserialize)] +pub struct User { + pub cid: String, + pub username: String, + pub groups: Vec, +} + +#[derive(Clone, Deserialize)] +pub struct Group { + pub name: String, + + #[serde(rename = "superGroup")] + pub super_group: SuperGroup, +} + +#[derive(Clone, Deserialize)] +pub struct SuperGroup { + pub name: String, +} + +fn check_status(resp: &Response) -> Result<(), String> { + if resp.status().is_server_error() { + Err(format!("Gamma Error: {}", resp.status())) + } else if resp.status().is_client_error() { + Err("Invalid credentials".to_string()) + } else { + Ok(()) + } +} + +pub(crate) async fn login(client: &mut Client, opt: &Opt, credentials: &Credentials) -> Result { + + let login_uri = format!("{}{}", opt.gamma, "/api/login"); + let login_resp = client + .post(&login_uri) + .form(credentials) + .send() + .await + .map_err(|e| format!("gamma: login failed: {}", e))?; + + debug!("gamma: tried logging in", { + username: credentials.username.as_str(), + uri: login_uri.as_str(), + response: format!("{:?}", login_resp).as_str(), + }); + + check_status(&login_resp)?; + + get_me(client, opt, &credentials.username).await +} + +pub (crate) async fn get_me(client: &mut Client, opt: &Opt, username: &str) -> Result { + let me_uri = format!("{}{}", opt.gamma, "/api/users/me"); + let me_resp = client + .get(&me_uri) + .send() + .await + .map_err(|e| format!("gamma: get user info failed: {}", e))?; + + debug!("gamma: tried getting user info", { + username: username, + uri: me_uri.as_str(), + response: format!("{:?}", me_resp).as_str(), + }); + + check_status(&me_resp)?; + + let user: User = me_resp + .json() + .await + .map_err(|e| format!("gamma: failed to deserialize json: {}", e))?; + + Ok(user) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a99aa01 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,317 @@ +mod cache; +mod gamma; +mod rules; + +use compound_error::CompoundError; +use hyper::service::{make_service_fn, service_fn}; +use hyper::{Body, Client, Request, Response, Server, Uri}; +use kv_log_macro::{debug, error, info, warn}; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::path::PathBuf; + +use cache::UserCache; +use gamma::{Credentials, User}; +use rules::{Method, Permission, Rule}; + +#[derive(Debug)] +struct Opt { + /// Example: http://my-server:80/ + proxy: String, + + /// Example: https://gamma.chalmers.it + gamma: String, + + /// Example: "My special place" + realm: String, + + rules: HashMap, +} + +#[derive(Debug, CompoundError)] +enum Error { + Hyper(hyper::Error), + IO(std::io::Error), +} +struct State { + opt: Opt, + cache: UserCache, +} + +async fn proxy_pass(mut req: Request, state: &State) -> Result, Error> { + let req_uri = req.uri().clone(); + + info!("{:#?}", req); + + let unauthorized = |msg| { + info!("respoinding with 401 Unauthorized due to {}", msg); + Ok(Response::builder() + .status(401) + .header( + "WWW-Authenticate", + format!(r#"Basic realm="{}", charset="UTF-8""#, state.opt.realm), + ) + .header("Docker-Distribution-Api-Version", "registry/2.0") + .body(Body::from("Unauthorized")) + .expect("infallible response")) + }; + + let forbidden = |msg| { + info!("respoinding with 403 Forbidden due to {}", msg); + Ok(Response::builder() + .status(403) + .body(Body::from("Forbidden")) + .expect("infallible response")) + }; + + let login = match req.headers_mut().remove("Authorization") { + None => { + info!("request received", { + uri: format!("{}", req_uri).as_str(), + method: req.method().as_str(), + provided_auth: false, + }); + None + } + Some(authorization) => { + info!("request received", { + uri: format!("{}", req_uri).as_str(), + method: req.method().as_str(), + provided_auth: true, + }); + + let credentials = authorization + .to_str() + .ok() + .and_then(|s| s.strip_prefix("Basic ")) + .and_then(|s| base64::decode(s).ok()) + .and_then(|b| String::from_utf8(b).ok()); + + match credentials.as_ref().and_then(|s| s.split_once(":")) { + Some((user, pass)) => { + let credentials = Credentials { + username: user.to_string(), + password: pass.to_string(), + }; + match state.cache.login(&state.opt, &credentials).await { + Ok(user) => Some(user), + Err(e) => { + warn!("{}", e); + return unauthorized("invalid login"); + } + } + } + None => { + warn!("client did not provide valid a \"Authorization\" header"); + None + } + } + } + }; + + match validate(&state.opt, &req, &login) { + Validation::Allowed => { + info!("success"); /* success! continue to proxy */ + } + Validation::NotAllowed => return forbidden("failed validation"), + Validation::RequiresLogin => return unauthorized("not logged in"), + } + + let proxy_uri: Uri = state.opt.proxy.parse().expect("proxy uri"); + let mut new_uri = Uri::builder().authority(proxy_uri.authority().unwrap().clone()); + new_uri = new_uri.scheme(proxy_uri.scheme_str().unwrap_or("http")); + + if let Some(paq) = req_uri.path_and_query().cloned() { + new_uri = new_uri.path_and_query(paq); + } + + *req.uri_mut() = new_uri.build().expect("uri"); + + let client = Client::new(); + let mut error = None; + let response = match client.request(req).await { + Ok(response) => response, + Err(e) => { + error = Some(format!("{:?}", e)); + Response::builder() + .status(503) + .body("503 Service Unavailable".into()) + .expect("infallible response") + } + }; + + if let Some(e) = error { + warn!("{}", e); + } + + Ok(response) +} + +fn get_opt() -> Result { + let env = |name| std::env::var(name).map_err(|e| format!("{}: {}", name, e)); + + Ok(Opt { + proxy: env("PROXY_HOST")?, + realm: env("AUTH_REALM")?, + gamma: env("GAMMA_HOST")?, + rules: std::env::vars() + .filter(|(name, _)| name.starts_with("AUTH_RULE_")) + .map(|(name, rule)| Rule::parse(&rule).map(|rule| (name, rule))) + .collect::>()?, + }) +} + +enum Validation { + /// Validataion succeeded, client pay proceed + Allowed, + + /// The client is not allowed to proceed, even if it provided credentials + NotAllowed, + + /// The client should provide credentials and try again + RequiresLogin, +} + +fn validate(opt: &Opt, req: &Request, login: &Option) -> Validation { + // filter irrelevant rules + let mut applicable_rules: Vec<_> = opt + .rules + .iter() + .filter(|(_, rule)| { + let req_path: PathBuf = req.uri().path().into(); + req_path.starts_with(&rule.path) + }) + .filter(|(_, rule)| match rule.method { + Method::Any => true, + Method::GET => req.method() == hyper::Method::GET, + Method::POST => req.method() == hyper::Method::POST, + Method::PUT => req.method() == hyper::Method::PUT, + Method::DELETE => req.method() == hyper::Method::DELETE, + Method::HEAD => req.method() == hyper::Method::HEAD, + Method::OPTIONS => req.method() == hyper::Method::OPTIONS, + Method::CONNECT => req.method() == hyper::Method::CONNECT, + Method::PATCH => req.method() == hyper::Method::PATCH, + Method::TRACE => req.method() == hyper::Method::TRACE, + }) + .collect(); + + // find the most relevant rule + applicable_rules.sort_by(|&(a_name, a), &(b_name, b)| { + // example priorities: + // 1. /foo/bar/baz/ POST + // 2. /foo/bar/baz/ Any + // 3. /foo/bar Any + + let a_len = a.path.components().count(); + let b_len = b.path.components().count(); + + a_len.cmp(&b_len).then_with(|| match (a.method, b.method) { + (ma, mb) if ma == mb => panic!("conflicting rules, {} and {}", a_name, b_name), + (Method::Any, _) => Ordering::Less, + (_, Method::Any) => Ordering::Greater, + _ => unreachable!("Rules for different methods can't conflict"), + }) + }); + + debug!("list of applicable rules: {:?}", applicable_rules); + + let (name, rule) = match applicable_rules.last() { + Some(&last) => last, + + // No rules exist, so we default to not allowed + None => return Validation::NotAllowed, + }; + + info!("checking connecting against rule {}", name); + + fn check_logged_in(login: &Option) -> Validation { + if login.is_some() { + Validation::Allowed + } else { + Validation::RequiresLogin + } + } + + fn check_is_group(group: &str, login: &Option) -> Validation { + match login.as_ref() { + Some(user) => { + info!("check_is_group", { user: user.username.as_str(), group: group }); + let mut groups = user + .groups + .iter() + .flat_map(|group| [&group.name, &group.super_group.name]); + + if groups.find(|&user_group| user_group == group).is_some() { + Validation::Allowed + } else { + Validation::NotAllowed + } + } + None => Validation::RequiresLogin, + } + } + + fn check_permission(permission: &Permission, login: &Option) -> Validation { + let recurse = |perm| check_permission(perm, login); + + match permission { + Permission::AllowAll => Validation::Allowed, + Permission::AnyUser => check_logged_in(login), + Permission::Group(group) => check_is_group(&group, login), + Permission::And(a, b) => match (recurse(a), recurse(b)) { + (Validation::Allowed, Validation::Allowed) => Validation::Allowed, + (Validation::NotAllowed, _) | (_, Validation::NotAllowed) => Validation::NotAllowed, + (Validation::RequiresLogin, _) | (_, Validation::RequiresLogin) => { + Validation::RequiresLogin + } + }, + Permission::Or(a, b) => match (recurse(a), recurse(b)) { + (Validation::NotAllowed, Validation::NotAllowed) => Validation::NotAllowed, + (Validation::Allowed, _) | (_, Validation::Allowed) => Validation::Allowed, + (Validation::RequiresLogin, _) | (_, Validation::RequiresLogin) => { + Validation::RequiresLogin + } + }, + Permission::Not(rule) => match recurse(rule) { + Validation::Allowed => Validation::NotAllowed, + Validation::NotAllowed | Validation::RequiresLogin => Validation::Allowed, + }, + } + } + + // validate request against the rule + check_permission(&rule.permission, login) +} + +#[tokio::main] +async fn main() { + //femme::start(); + femme::with_level(femme::LevelFilter::Debug); + + let opt: Opt = match get_opt() { + Ok(opt) => opt, + Err(e) => { + error!("{}", e); + std::process::exit(1); + } + }; + info!("{:#?}", opt); + + let cache = UserCache::new(); + + let state: &'static State = Box::leak(Box::new(State { opt, cache })); + + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); + info!("Listening on 0.0.0.0:3000"); + + let make_svc = make_service_fn(|_conn| async move { + Ok::<_, Error>(service_fn(move |r| proxy_pass(r, state))) + }); + + let server = Server::bind(&addr).serve(make_svc); + + if let Err(e) = server.await { + error!("server error: {}", e); + } +} diff --git a/src/me.json b/src/me.json new file mode 100644 index 0000000..6dfce52 --- /dev/null +++ b/src/me.json @@ -0,0 +1,225 @@ +{ + "id": "e514bbda-97c8-4879-b62a-3e2c350f8032", + "cid": "hulthe", + "nick": "Tux🐧", + "firstName": "Joakim", + "lastName": "Hulthe", + "email": "joakim@hulthe.net", + "phone": "703644214", + "language": "sv", + "avatarUrl": "https://gamma.chalmers.it/api/uploads/8314961ee67fbfa57f603fff17ffbecc58bbf490.jpg", + "gdpr": true, + "userAgreement": true, + "accountLocked": false, + "acceptanceYear": 2016, + "authorities": [], + "activated": true, + "enabled": true, + "username": "hulthe", + "accountNonLocked": true, + "accountNonExpired": true, + "credentialsNonExpired": true, + "groups": [ + { + "id": "b6be176b-5705-49f5-8ac2-8646aec748ba", + "becomesActive": 1483142400000, + "becomesInactive": 1514678400000, + "description": { + "sv": "digIT 17/18", + "en": "digIT 17/18" + }, + "email": "digit17@chalmers.it", + "function": { + "sv": "digIT 17/18", + "en": "digIT 17/18" + }, + "name": "digit17", + "prettyName": "digIT 17/18", + "avatarURL": null, + "superGroup": { + "id": "e2eb1ee6-d1c9-4ec6-bbd1-fdd5910060c7", + "name": "didit", + "prettyName": "didIT", + "type": "ALUMNI", + "email": "didit@chalmers.it" + }, + "active": false + }, + { + "id": "6e0f5714-65b9-46e1-8d6d-0273487bea48", + "becomesActive": 1568764800000, + "becomesInactive": 1603065600000, + "description": { + "sv": "Dataskyddsombud 2019", + "en": "Dataskyddsombud 2019" + }, + "email": "dpo19@chalmers.it", + "function": { + "sv": "Dataskyddsombud 2019", + "en": "Dataskyddsombud 2019" + }, + "name": "dpo19", + "prettyName": "Dataskyddsombud 2019", + "avatarURL": null, + "superGroup": { + "id": "6586de15-94a3-4c7a-bd31-e50854d0b5eb", + "name": "dpo", + "prettyName": "dpo", + "type": "COMMITTEE", + "email": "dpo@chalmers.it" + }, + "active": false + }, + { + "id": "d45861bb-938a-47bd-a4ce-d5152c697278", + "becomesActive": 1582329600000, + "becomesInactive": 1617321600000, + "description": { + "sv": "DrawIT 20", + "en": "DrawIT 20" + }, + "email": "drawit20@chalmers.it", + "function": { + "sv": "DrawIT 20", + "en": "DrawIT 20" + }, + "name": "drawit20", + "prettyName": "DrawIT 20", + "avatarURL": null, + "superGroup": { + "id": "25502aca-9f41-4110-a29d-04aeeed93411", + "name": "dragit", + "prettyName": "DragIT", + "type": "ALUMNI", + "email": "dragit@chalmers.it" + }, + "active": false + }, + { + "id": "4da2800d-72e9-4052-a857-f95cca89fca5", + "becomesActive": 1451692800000, + "becomesInactive": 1483315200000, + "description": { + "sv": "LaggIT 2016", + "en": "LaggIT 2016" + }, + "email": "laggit16@chalmers.it", + "function": { + "sv": "LaggIT 2016", + "en": "LaggIT 2016" + }, + "name": "laggit16", + "prettyName": "LaggIT 16", + "avatarURL": null, + "superGroup": { + "id": "d7bb514c-31e9-4af3-9119-3fa03f643ea5", + "name": "ragequit", + "prettyName": "ragequIT", + "type": "ALUMNI", + "email": "ragequit@chalmers.it" + }, + "active": false + }, + { + "id": "8015807b-8680-4f0f-8bd1-d2bf1ffbc130", + "becomesActive": 1514851200000, + "becomesInactive": 1546387200000, + "description": { + "sv": "laggIT 2018", + "en": "laggIT 2018" + }, + "email": "laggit18@chalmers.it", + "function": { + "sv": "laggIT 2018", + "en": "laggIT 2018" + }, + "name": "laggit18", + "prettyName": "laggIT 18", + "avatarURL": null, + "superGroup": { + "id": "d7bb514c-31e9-4af3-9119-3fa03f643ea5", + "name": "ragequit", + "prettyName": "ragequIT", + "type": "ALUMNI", + "email": "ragequit@chalmers.it" + }, + "active": false + }, + { + "id": "ef3054b0-78cb-494a-8352-2c2ee62b54b0", + "becomesActive": 1483228800000, + "becomesInactive": 1514764800000, + "description": { + "sv": "LaggIT 17", + "en": "LaggIT 17" + }, + "email": "laggit17@chalmers.it", + "function": { + "sv": "LaggIT 17", + "en": "LaggIT 17" + }, + "name": "laggit17", + "prettyName": "LaggIT 17", + "avatarURL": null, + "superGroup": { + "id": "d7bb514c-31e9-4af3-9119-3fa03f643ea5", + "name": "ragequit", + "prettyName": "ragequIT", + "type": "ALUMNI", + "email": "ragequit@chalmers.it" + }, + "active": false + }, + { + "id": "c246af65-531d-49e5-9b3a-9e97cb5b32b0", + "becomesActive": 1616803200000, + "becomesInactive": 1651363200000, + "description": { + "sv": "", + "en": "" + }, + "email": "drawit21@chalmers.it", + "function": { + "sv": "", + "en": "" + }, + "name": "drawit 21", + "prettyName": "DrawIT 21", + "avatarURL": null, + "superGroup": { + "id": "09728fbb-fead-4f12-aec9-7bfe39c8cc6f", + "name": "drawit", + "prettyName": "DrawIT", + "type": "SOCIETY", + "email": "drawit@chalmers.it" + }, + "active": true + }, + { + "id": "b6756bd1-8fab-48e6-9204-1bf0b097c4e1", + "becomesActive": 1546214400000, + "becomesInactive": 1577750400000, + "description": { + "sv": "DrawIT 19", + "en": "DrawIT 19" + }, + "email": "drawit19@chalmers.it", + "function": { + "sv": "DrawIT 19", + "en": "DrawIT 19" + }, + "name": "drawit19", + "prettyName": "DrawIT 19", + "avatarURL": null, + "superGroup": { + "id": "25502aca-9f41-4110-a29d-04aeeed93411", + "name": "dragit", + "prettyName": "DragIT", + "type": "ALUMNI", + "email": "dragit@chalmers.it" + }, + "active": false + } + ], + "websiteURLs": null +} \ No newline at end of file diff --git a/src/rules.rs b/src/rules.rs new file mode 100644 index 0000000..f3ead3a --- /dev/null +++ b/src/rules.rs @@ -0,0 +1,90 @@ +//! Rules for configuring who has access to what endpoints +//! +//! ## Examples: +//! ``` +//! Rule { path: "/v2/", method: GET, permission: And(AnyUser, Not(Group("styrit"))) } +//! Rule { path: "/v2/_catalog", permission: AllowAll } +//! Rule { path: "/v2/image/*/push", permission: Or(Group("didit"), Group("digit")) } +//! ``` + +use serde::{Deserialize, Serialize}; +use std::fmt::{self, Display, Formatter}; +use std::path::PathBuf; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Rule { + pub path: PathBuf, + + #[serde(default)] + pub method: Method, + + pub permission: Permission, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum Method { + GET, + POST, + PUT, + DELETE, + HEAD, + OPTIONS, + CONNECT, + PATCH, + TRACE, + Any, +} + +impl Default for Method { + fn default() -> Self { + Method::Any + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Permission { + /// No authorization required + AllowAll, + + /// Required the user to be logged in + AnyUser, + + /// Requires the user to be logged in and part of the specific group + Group(String), + + Or(Box, Box), + And(Box, Box), + Not(Box), +} + +impl Rule { + pub fn parse(s: &str) -> Result { + ron::from_str(s).map_err(|e| format!("Failed to parse Rule: {}", e)) + } +} + +impl Display for Rule { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self.method { + Method::Any => {} + _ => write!(f, "{:?} ", self.method)?, + } + + write!(f, "{:?} ", self.path)?; + write!(f, "permission: {}", self.permission)?; + Ok(()) + } +} + +impl Display for Permission { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Permission::AllowAll => write!(f, "allow all"), + Permission::Group(group) => write!(f, "is group '{}'", group), + Permission::AnyUser => write!(f, "is logged in"), + Permission::Or(a, b) => write!(f, "({}) or ({})", a, b), + Permission::And(a, b) => write!(f, "({}) and ({})", a, b), + Permission::Not(perm) => write!(f, "not {}", perm), + } + } +}