From aab1f4937470ee0f46e32c9d66569c3f7cb28b13 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 2 Jun 2022 16:30:34 +0200 Subject: [PATCH] Support for applying OPA policies during client registration --- .gitattributes | 1 + Cargo.lock | 810 ++++++++++++++++++++- crates/cli/Cargo.toml | 1 + crates/cli/src/commands/server.rs | 16 + crates/config/src/sections/mod.rs | 8 + crates/config/src/sections/policy.rs | 90 +++ crates/handlers/Cargo.toml | 1 + crates/handlers/src/lib.rs | 9 +- crates/handlers/src/oauth2/registration.rs | 21 +- crates/policy/Cargo.toml | 19 + crates/policy/src/lib.rs | 175 +++++ policies/Makefile | 5 + policies/client_registration.rego | 19 + policies/login.rego | 3 + policies/policy.wasm | Bin 0 -> 132932 bytes policies/register.rego | 3 + 16 files changed, 1153 insertions(+), 28 deletions(-) create mode 100644 .gitattributes create mode 100644 crates/config/src/sections/policy.rs create mode 100644 crates/policy/Cargo.toml create mode 100644 crates/policy/src/lib.rs create mode 100644 policies/Makefile create mode 100644 policies/client_registration.rego create mode 100644 policies/login.rego create mode 100644 policies/policy.wasm create mode 100644 policies/register.rego diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..7e11055a --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.wasm binary diff --git a/Cargo.lock b/Cargo.lock index 80e3f70c..4958875c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -107,9 +116,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8589c784ff02ac80dafc5e4116c3a2a3743ac5e0c902483518a88eec6559cf99" +checksum = "345fd392ab01f746c717b1357165b76f0b67a60192007b234058c9045fdcf695" dependencies = [ "brotli", "flate2", @@ -542,6 +551,21 @@ dependencies = [ "syn", ] +[[package]] +name = "backtrace" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base16ct" version = "0.1.1" @@ -658,9 +682,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byte-tools" @@ -708,6 +732,9 @@ name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -879,6 +906,15 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpufeatures" version = "0.2.2" @@ -888,6 +924,95 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-bforest" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fa7c3188913c2d11a361e0431e135742372a2709a99b103e79758e11a0a797e" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29285f70fd396a8f64455a15a6e1d390322e4a5f5186de513141313211b0a23e" +dependencies = [ + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "gimli", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057eac2f202ec95aebfd8d495e88560ac085f6a415b3c6c28529dc5eb116a141" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d93869efd18874a9341cfd8ad66bcb08164e86357a694a0e939d29e87410b9" + +[[package]] +name = "cranelift-entity" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e34bd7a1fefa902c90a921b36323f17a398b788fa56a75f07a29d83b6e28808" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457018dd2d6ee300953978f63215b5edf3ae42dbdf8c7c038972f10394599f72" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-native" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba027cc41bf1d0eee2ddf16caba2ee1be682d0214520fff0129d2c6557fda89" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b17639ced10b9916c9be120d38c872ea4f9888aa09248568b10056ef0559bfa" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser", + "wasmtime-types", +] + [[package]] name = "crc" version = "2.1.0" @@ -937,6 +1062,31 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + [[package]] name = "crossbeam-queue" version = "0.3.5" @@ -959,9 +1109,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3e45371ac6a1370324b085acb466f24b07d4d66da6999a42c8ce655bd14e0e" +checksum = "78a4e0fb04deabeb711eb20bd1179f1524c06f7e6975ebccc495f678a635887b" dependencies = [ "generic-array 0.14.5", "rand_core", @@ -1126,6 +1276,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs" version = "4.0.0" @@ -1146,6 +1306,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -1219,6 +1390,40 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.2" @@ -1231,6 +1436,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "1.7.0" @@ -1266,6 +1477,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "file-per-thread-logger" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -1274,13 +1495,11 @@ checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" [[package]] name = "flate2" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ - "cfg-if", "crc32fast", - "libc", "miniz_oxide", ] @@ -1407,6 +1626,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -1447,6 +1675,17 @@ dependencies = [ "polyval", ] +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "globset" version = "0.4.8" @@ -1666,6 +1905,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" +[[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.19" @@ -1770,12 +2015,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] @@ -1814,6 +2060,12 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" +[[package]] +name = "io-lifetimes" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" + [[package]] name = "iovec" version = "0.1.4" @@ -1859,6 +2111,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "ittapi-rs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f712648a1ad72fbfb7adc2772c331e8d90f022f8cf30cbabefba2878dd3172b0" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.57" @@ -1868,6 +2138,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d" +dependencies = [ + "serde", + "serde_json", + "treediff", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1886,6 +2167,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "lettre" version = "0.10.0-rc.6" @@ -1933,6 +2220,12 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "linux-raw-sys" +version = "0.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" + [[package]] name = "lock_api" version = "0.4.7" @@ -1952,6 +2245,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "maplit" version = "1.0.2" @@ -2009,6 +2311,7 @@ dependencies = [ "mas-email", "mas-handlers", "mas-http", + "mas-policy", "mas-router", "mas-static-files", "mas-storage", @@ -2118,6 +2421,7 @@ dependencies = [ "mas-http", "mas-iana", "mas-jose", + "mas-policy", "mas-router", "mas-storage", "mas-templates", @@ -2228,6 +2532,22 @@ dependencies = [ "url", ] +[[package]] +name = "mas-policy" +version = "0.1.0" +dependencies = [ + "anyhow", + "mas-data-model", + "oauth2-types", + "opa-wasm", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "wasmtime", +] + [[package]] name = "mas-router" version = "0.1.0" @@ -2351,6 +2671,24 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memfd" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6627dc657574b49d6ad27105ed671822be56e0d2547d413bfbf3e8d8fa92e7a" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "mime" version = "0.3.16" @@ -2375,9 +2713,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", ] @@ -2394,6 +2732,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + [[package]] name = "multimap" version = "0.8.3" @@ -2498,12 +2842,48 @@ dependencies = [ "url", ] +[[package]] +name = "object" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +dependencies = [ + "crc32fast", + "hashbrown", + "indexmap", + "memchr", +] + [[package]] name = "once_cell" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +[[package]] +name = "opa-wasm" +version = "0.1.0" +source = "git+https://github.com/matrix-org/rust-opa-wasm.git#47586f82ca39390aeed7bb56b8220395899f3176" +dependencies = [ + "anyhow", + "base64", + "digest 0.10.3", + "hex", + "hmac", + "json-patch", + "md-5", + "semver", + "serde", + "serde_json", + "sha1", + "sha2 0.10.2", + "sprintf", + "thiserror", + "tokio", + "tracing", + "wasmtime", +] + [[package]] name = "opaque-debug" version = "0.2.3" @@ -2634,9 +3014,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" [[package]] name = "p256" @@ -2662,9 +3042,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core 0.9.3", @@ -2832,9 +3212,9 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", "indexmap", @@ -3060,6 +3440,15 @@ dependencies = [ "prost", ] +[[package]] +name = "psm" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871372391786ccec00d3c5d3d6608905b3d4db263639cfe075d3b60a736d115a" +dependencies = [ + "cc", +] + [[package]] name = "quote" version = "1.0.18" @@ -3105,6 +3494,30 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.13" @@ -3126,10 +3539,22 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.5.5" +name = "regalloc2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "904196c12c9f55d3aea578613219f493ced8e05b3d0c6a42d11cb4142d8b4879" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" dependencies = [ "aho-corasick", "memchr", @@ -3147,9 +3572,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] [[package]] name = "remove_dir_all" @@ -3278,6 +3715,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustc_version" version = "0.4.0" @@ -3287,6 +3730,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.33.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "winapi", +] + [[package]] name = "rustls" version = "0.19.1" @@ -3601,6 +4058,17 @@ dependencies = [ "digest 0.10.3", ] +[[package]] +name = "sha1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3665,6 +4133,12 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +[[package]] +name = "slice-group-by" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" + [[package]] name = "slug" version = "0.1.4" @@ -3706,6 +4180,12 @@ dependencies = [ "der", ] +[[package]] +name = "sprintf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "082cb9266c8691c70a98442f8f893ba3549f02f64e128cb096dac92e1c428340" + [[package]] name = "sqlformat" version = "0.1.8" @@ -3813,6 +4293,12 @@ dependencies = [ "tokio-rustls 0.22.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stringprep" version = "0.1.2" @@ -3875,6 +4361,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" +[[package]] +name = "target-lexicon" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" + [[package]] name = "tempfile" version = "3.3.0" @@ -4033,7 +4525,7 @@ dependencies = [ "mio", "num_cpus", "once_cell", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2", @@ -4125,6 +4617,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "tonic" version = "0.6.2" @@ -4325,6 +4826,15 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "treediff" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +dependencies = [ + "serde_json", +] + [[package]] name = "try-lock" version = "0.2.3" @@ -4449,6 +4959,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -4601,6 +5117,219 @@ version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +[[package]] +name = "wasmparser" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77dc97c22bb5ce49a47b745bed8812d30206eff5ef3af31424f2c1820c0974b2" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmtime" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdd1101bdfa0414a19018ec0a091951a20b695d4d04f858d49f6c4cc53cd8dd" +dependencies = [ + "anyhow", + "async-trait", + "backtrace", + "bincode", + "cfg-if", + "indexmap", + "lazy_static", + "libc", + "log", + "object", + "once_cell", + "paste", + "psm", + "rayon", + "region", + "serde", + "target-lexicon", + "wasmparser", + "wasmtime-cache", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit", + "wasmtime-runtime", + "wat", + "winapi", +] + +[[package]] +name = "wasmtime-cache" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79da81ed0724392948ad7a0fb5088ff1bd15fa937356c8c037c6b1c8b5473cde" +dependencies = [ + "anyhow", + "base64", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix", + "serde", + "sha2 0.9.9", + "toml", + "winapi", + "zstd", +] + +[[package]] +name = "wasmtime-cranelift" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e78edcfb0daa9a9579ac379d00e2d5a5b2a60c0d653c8c95e8412f2166acb9" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "more-asserts", + "object", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4201389132ec467981980549574b33fc70d493b40f2c045c8ce5c7b54fbad97e" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli", + "indexmap", + "log", + "more-asserts", + "object", + "serde", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-fiber" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba6777a84b44f9a384b5c9d511ae3d86534438b7e25d928b8e8e858ecad5df2" +dependencies = [ + "cc", + "rustix", + "winapi", +] + +[[package]] +name = "wasmtime-jit" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1587ca7752d00862faa540d00fd28e5ccf1ac61ba19756449193f1153cb2b127" +dependencies = [ + "addr2line", + "anyhow", + "bincode", + "cfg-if", + "cpp_demangle", + "gimli", + "ittapi-rs", + "log", + "object", + "region", + "rustc-demangle", + "rustix", + "serde", + "target-lexicon", + "thiserror", + "wasmtime-environ", + "wasmtime-jit-debug", + "wasmtime-runtime", + "winapi", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b27233ab6c8934b23171c64f215f902ef19d18c1712b46a0674286d1ef28d5dd" +dependencies = [ + "lazy_static", + "object", + "rustix", +] + +[[package]] +name = "wasmtime-runtime" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d3b0b8f13db47db59d616e498fe45295819d04a55f9921af29561827bdb816" +dependencies = [ + "anyhow", + "backtrace", + "cc", + "cfg-if", + "indexmap", + "libc", + "log", + "mach", + "memfd", + "memoffset", + "more-asserts", + "rand", + "region", + "rustix", + "thiserror", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-debug", + "winapi", +] + +[[package]] +name = "wasmtime-types" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1630d9dca185299bec7f557a7e73b28742fe5590caf19df001422282a0a98ad1" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser", +] + +[[package]] +name = "wast" +version = "41.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f882898b8b817cc4edc16aa3692fdc087b356edc8cc0c2164f5b5181e31c3870" +dependencies = [ + "leb128", + "memchr", + "unicode-width", +] + +[[package]] +name = "wat" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48b3b9b3e39e66c7fd3f8be785e74444d216260f491e93369e317ed6482ff80f" +dependencies = [ + "wast", +] + [[package]] name = "watchman_client" version = "0.7.2" @@ -4797,3 +5526,32 @@ name = "zeroize" version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.1+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +dependencies = [ + "cc", + "libc", +] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index eac83046..312a7784 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -36,6 +36,7 @@ mas-config = { path = "../config" } mas-email = { path = "../email" } mas-handlers = { path = "../handlers" } mas-http = { path = "../http" } +mas-policy = { path = "../policy" } mas-router = { path = "../router" } mas-static-files = { path = "../static-files" } mas-storage = { path = "../storage" } diff --git a/crates/cli/src/commands/server.rs b/crates/cli/src/commands/server.rs index e614f867..9eab383f 100644 --- a/crates/cli/src/commands/server.rs +++ b/crates/cli/src/commands/server.rs @@ -25,6 +25,7 @@ use hyper::Server; use mas_config::RootConfig; use mas_email::{MailTransport, Mailer}; use mas_http::ServerLayer; +use mas_policy::PolicyFactory; use mas_router::UrlBuilder; use mas_storage::MIGRATOR; use mas_tasks::TaskQueue; @@ -176,6 +177,20 @@ impl Options { let encrypter = config.secrets.encrypter(); + // Load and compile the WASM policies + let mut policy = tokio::fs::File::open(&config.policy.wasm_module) + .await + .context("failed to open OPA WASM policy file")?; + let policy_factory = PolicyFactory::load( + &mut policy, + config.policy.data.clone().unwrap_or_default(), + config.policy.login_entrypoint.clone(), + config.policy.register_entrypoint.clone(), + config.policy.client_registration_entrypoint.clone(), + ) + .await?; + let policy_factory = Arc::new(policy_factory); + // Load and compile the templates let templates = Templates::load_from_config(&config.templates) .await @@ -217,6 +232,7 @@ impl Options { &mailer, &url_builder, &matrix_config, + &policy_factory, ) .fallback(static_files) .layer(ServerLayer::default()); diff --git a/crates/config/src/sections/mod.rs b/crates/config/src/sections/mod.rs index 73c0b0a9..6d11f2f9 100644 --- a/crates/config/src/sections/mod.rs +++ b/crates/config/src/sections/mod.rs @@ -22,6 +22,7 @@ mod database; mod email; mod http; mod matrix; +mod policy; mod secrets; mod telemetry; mod templates; @@ -33,6 +34,7 @@ pub use self::{ email::{EmailConfig, EmailSmtpMode, EmailTransportConfig}, http::HttpConfig, matrix::MatrixConfig, + policy::PolicyConfig, secrets::{Encrypter, SecretsConfig}, telemetry::{ MetricsConfig, MetricsExporterConfig, Propagator, TelemetryConfig, TracingConfig, @@ -79,6 +81,10 @@ pub struct RootConfig { /// Configuration related to the homeserver #[serde(default)] pub matrix: MatrixConfig, + + /// Configuration related to the OPA policies + #[serde(default)] + pub policy: PolicyConfig, } #[async_trait] @@ -98,6 +104,7 @@ impl ConfigurationSection<'_> for RootConfig { email: EmailConfig::generate().await?, secrets: SecretsConfig::generate().await?, matrix: MatrixConfig::generate().await?, + policy: PolicyConfig::generate().await?, }) } @@ -112,6 +119,7 @@ impl ConfigurationSection<'_> for RootConfig { email: EmailConfig::test(), secrets: SecretsConfig::test(), matrix: MatrixConfig::test(), + policy: PolicyConfig::test(), } } } diff --git a/crates/config/src/sections/policy.rs b/crates/config/src/sections/policy.rs new file mode 100644 index 00000000..6ae4a160 --- /dev/null +++ b/crates/config/src/sections/policy.rs @@ -0,0 +1,90 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::path::PathBuf; + +use async_trait::async_trait; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +use super::ConfigurationSection; + +fn default_wasm_module() -> PathBuf { + "./policies/policy.wasm".into() +} + +fn default_client_registration_endpoint() -> String { + "client_registration/allow".to_string() +} + +fn default_login_endpoint() -> String { + "login/allow".to_string() +} + +fn default_register_endpoint() -> String { + "register/allow".to_string() +} + +/// Application secrets +#[serde_as] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PolicyConfig { + /// Path to the WASM module + #[serde(default = "default_wasm_module")] + pub wasm_module: PathBuf, + + /// Entrypoint to use when evaluating client registrations + #[serde(default = "default_client_registration_endpoint")] + pub client_registration_entrypoint: String, + + /// Entrypoint to use when evaluating user logins + #[serde(default = "default_login_endpoint")] + pub login_entrypoint: String, + + /// Entrypoint to use when evaluating user registrations + #[serde(default = "default_register_endpoint")] + pub register_entrypoint: String, + + /// Arbitrary data to pass to the policy + #[serde(default)] + pub data: Option, +} + +impl Default for PolicyConfig { + fn default() -> Self { + Self { + wasm_module: default_wasm_module(), + client_registration_entrypoint: default_client_registration_endpoint(), + login_entrypoint: default_login_endpoint(), + register_entrypoint: default_register_endpoint(), + data: None, + } + } +} + +#[async_trait] +impl ConfigurationSection<'_> for PolicyConfig { + fn path() -> &'static str { + "policy" + } + + async fn generate() -> anyhow::Result { + Ok(Self::default()) + } + + fn test() -> Self { + Self::default() + } +} diff --git a/crates/handlers/Cargo.toml b/crates/handlers/Cargo.toml index 8f6cf9f0..8e7d9fde 100644 --- a/crates/handlers/Cargo.toml +++ b/crates/handlers/Cargo.toml @@ -62,6 +62,7 @@ mas-email = { path = "../email" } mas-http = { path = "../http" } mas-iana = { path = "../iana" } mas-jose = { path = "../jose" } +mas-policy = { path = "../policy" } mas-storage = { path = "../storage" } mas-templates = { path = "../templates" } mas-router = { path = "../router" } diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index fd44500e..7b03cd26 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -34,6 +34,7 @@ use mas_config::{Encrypter, MatrixConfig}; use mas_email::Mailer; use mas_http::CorsLayerExt; use mas_jose::StaticKeystore; +use mas_policy::PolicyFactory; use mas_router::{Route, UrlBuilder}; use mas_templates::{ErrorContext, Templates}; use sqlx::PgPool; @@ -46,7 +47,11 @@ mod oauth2; mod views; #[must_use] -#[allow(clippy::too_many_lines, clippy::missing_panics_doc)] +#[allow( + clippy::too_many_lines, + clippy::missing_panics_doc, + clippy::too_many_arguments +)] pub fn router( pool: &PgPool, templates: &Templates, @@ -55,6 +60,7 @@ pub fn router( mailer: &Mailer, url_builder: &UrlBuilder, matrix_config: &MatrixConfig, + policy_factory: &Arc, ) -> Router where B: HttpBody + Send + 'static, @@ -233,4 +239,5 @@ where .layer(Extension(url_builder.clone())) .layer(Extension(mailer.clone())) .layer(Extension(matrix_config.clone())) + .layer(Extension(policy_factory.clone())) } diff --git a/crates/handlers/src/oauth2/registration.rs b/crates/handlers/src/oauth2/registration.rs index fdb34fee..115d50fd 100644 --- a/crates/handlers/src/oauth2/registration.rs +++ b/crates/handlers/src/oauth2/registration.rs @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; + use axum::{response::IntoResponse, Extension, Json}; use hyper::StatusCode; use mas_iana::oauth::{OAuthAuthorizationEndpointResponseType, OAuthClientAuthenticationMethod}; +use mas_policy::PolicyFactory; use mas_storage::oauth2::client::insert_client; use oauth2_types::{ errors::{INVALID_CLIENT_METADATA, INVALID_REDIRECT_URI, SERVER_ERROR}, @@ -31,11 +34,17 @@ pub(crate) enum RouteError { #[error(transparent)] Internal(Box), + #[error(transparent)] + Anyhow(#[from] anyhow::Error), + #[error("invalid redirect uri")] InvalidRedirectUri, #[error("invalid client metadata")] InvalidClientMetadata, + + #[error("denied by the policy")] + PolicyDenied, } impl From for RouteError { @@ -47,9 +56,12 @@ impl From for RouteError { impl IntoResponse for RouteError { fn into_response(self) -> axum::response::Response { match self { - Self::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, Json(SERVER_ERROR)), + Self::Internal(_) | Self::Anyhow(_) => { + (StatusCode::INTERNAL_SERVER_ERROR, Json(SERVER_ERROR)) + } Self::InvalidRedirectUri => (StatusCode::BAD_REQUEST, Json(INVALID_REDIRECT_URI)), Self::InvalidClientMetadata => (StatusCode::BAD_REQUEST, Json(INVALID_CLIENT_METADATA)), + Self::PolicyDenied => (StatusCode::UNAUTHORIZED, Json(INVALID_CLIENT_METADATA)), } .into_response() } @@ -58,6 +70,7 @@ impl IntoResponse for RouteError { #[tracing::instrument(skip_all, err)] pub(crate) async fn post( Extension(pool): Extension, + Extension(policy_factory): Extension>, Json(body): Json, ) -> Result { info!(?body, "Client registration"); @@ -105,6 +118,12 @@ pub(crate) async fn post( return Err(RouteError::InvalidClientMetadata); } + let mut policy = policy_factory.instanciate().await?; + let allowed = policy.evaluate_client_registration(&body).await?; + if !allowed { + return Err(RouteError::PolicyDenied); + } + // Grab a txn let mut txn = pool.begin().await?; diff --git a/crates/policy/Cargo.toml b/crates/policy/Cargo.toml new file mode 100644 index 00000000..968fd093 --- /dev/null +++ b/crates/policy/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mas-policy" +version = "0.1.0" +authors = ["Quentin Gliech "] +edition = "2021" +license = "Apache-2.0" + +[dependencies] +anyhow = "1.0.31" +opa-wasm = { git = "https://github.com/matrix-org/rust-opa-wasm.git" } +serde = { version = "1.0.31", features = ["derive"] } +serde_json = "1.0.31" +thiserror = "1.0.31" +tokio = { version = "1.18.2", features = ["io-util", "rt"] } +tracing = "0.1.34" +wasmtime = "0.37.0" + +mas-data-model = { path = "../data-model" } +oauth2-types = { path = "../oauth2-types" } diff --git a/crates/policy/src/lib.rs b/crates/policy/src/lib.rs new file mode 100644 index 00000000..2802a975 --- /dev/null +++ b/crates/policy/src/lib.rs @@ -0,0 +1,175 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::bail; +use oauth2_types::registration::ClientMetadata; +use opa_wasm::Runtime; +use serde::Deserialize; +use thiserror::Error; +use tokio::io::{AsyncRead, AsyncReadExt}; +use wasmtime::{Config, Engine, Module, Store}; + +#[derive(Debug, Error)] +pub enum LoadError { + #[error("failed to read module")] + Read(#[from] tokio::io::Error), + + #[error("failed to create WASM engine")] + Engine(#[source] anyhow::Error), + + #[error("module compilation task crashed")] + CompilationTask(#[from] tokio::task::JoinError), + + #[error("failed to compile WASM module")] + Compilation(#[source] anyhow::Error), +} + +pub struct PolicyFactory { + engine: Engine, + module: Module, + data: serde_json::Value, + login_entrypoint: String, + register_entrypoint: String, + client_registration_entrypoint: String, +} + +impl PolicyFactory { + pub async fn load( + mut source: impl AsyncRead + std::marker::Unpin, + data: serde_json::Value, + login_entrypoint: String, + register_entrypoint: String, + client_registration_entrypoint: String, + ) -> Result { + let mut config = Config::default(); + config.async_support(true); + config.cranelift_opt_level(wasmtime::OptLevel::Speed); + let engine = Engine::new(&config).map_err(LoadError::Engine)?; + let mut buf = Vec::new(); + source.read_to_end(&mut buf).await?; + let (engine, module) = tokio::task::spawn_blocking(move || { + let module = Module::new(&engine, buf); + (engine, module) + }) + .await?; + let module = module.map_err(LoadError::Compilation)?; + + Ok(Self { + engine, + module, + data, + login_entrypoint, + register_entrypoint, + client_registration_entrypoint, + }) + } + + pub async fn instanciate(&self) -> Result { + let mut store = Store::new(&self.engine, ()); + let runtime = Runtime::new(&mut store, &self.module).await?; + + // Check that we have the required entrypoints + let entrypoints = runtime.entrypoints(); + + for e in [ + self.login_entrypoint.as_str(), + self.register_entrypoint.as_str(), + ] { + if !entrypoints.contains(e) { + bail!("missing entrypoint {e}") + } + } + + let instance = runtime.with_data(&mut store, &self.data).await?; + + Ok(Policy { + store, + instance, + login_entrypoint: self.login_entrypoint.clone(), + register_entrypoint: self.register_entrypoint.clone(), + client_registration_entrypoint: self.client_registration_entrypoint.clone(), + }) + } +} + +#[derive(Deserialize)] +struct EvaluationResult { + result: bool, +} + +pub struct Policy { + store: Store<()>, + instance: opa_wasm::Policy, + login_entrypoint: String, + register_entrypoint: String, + client_registration_entrypoint: String, +} + +impl Policy { + pub async fn evaluate_login( + &mut self, + user: &mas_data_model::User<()>, + ) -> Result { + let user = serde_json::to_value(user)?; + let input = serde_json::json!({ "user": user }); + + let [res]: [EvaluationResult; 1] = self + .instance + .evaluate(&mut self.store, &self.login_entrypoint, &input) + .await?; + + Ok(res.result) + } + + pub async fn evaluate_register( + &mut self, + username: &str, + email: &str, + ) -> Result { + let input = serde_json::json!({ + "user": { + "username": username, + "email": email + } + }); + + let [res]: [EvaluationResult; 1] = self + .instance + .evaluate(&mut self.store, &self.register_entrypoint, &input) + .await?; + + Ok(res.result) + } + + pub async fn evaluate_client_registration( + &mut self, + client_metadata: &ClientMetadata, + ) -> Result { + let client_metadata = serde_json::to_value(client_metadata)?; + let input = serde_json::json!({ + "client_metadata": client_metadata, + }); + + let [res]: [EvaluationResult; 1] = self + .instance + .evaluate( + &mut self.store, + &self.client_registration_entrypoint, + &input, + ) + .await?; + + Ok(res.result) + } +} diff --git a/policies/Makefile b/policies/Makefile new file mode 100644 index 00000000..34f228a2 --- /dev/null +++ b/policies/Makefile @@ -0,0 +1,5 @@ +policy.wasm: client_registration.rego login.rego register.rego + opa build -t wasm -e "client_registration/allow" -e "login/allow" -e "register/allow" $^ + tar xzf bundle.tar.gz /policy.wasm + rm -f bundle.tar.gz + touch $@ diff --git a/policies/client_registration.rego b/policies/client_registration.rego new file mode 100644 index 00000000..37cf5f16 --- /dev/null +++ b/policies/client_registration.rego @@ -0,0 +1,19 @@ +package client_registration + +import future.keywords.in + + +secure_url(x) { + is_string(x) + startswith(x, "https://") +} + +default allow := false + +allow { + secure_url(input.client_metadata.client_uri) + secure_url(input.client_metadata.tos_uri) + secure_url(input.client_metadata.policy_uri) + some redirect_uri in input.client_metadata.redirect_uris + secure_url(redirect_uri) +} diff --git a/policies/login.rego b/policies/login.rego new file mode 100644 index 00000000..8154fff2 --- /dev/null +++ b/policies/login.rego @@ -0,0 +1,3 @@ +package login + +allow := true diff --git a/policies/policy.wasm b/policies/policy.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2e50bf6e7f21733854f48c75cf4f31b4f0f93633 GIT binary patch literal 132932 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 literal 0 HcmV?d00001 diff --git a/policies/register.rego b/policies/register.rego new file mode 100644 index 00000000..8e6aa412 --- /dev/null +++ b/policies/register.rego @@ -0,0 +1,3 @@ +package register + +allow := true