diff --git a/.github/workflows/functional.yml b/.github/workflows/functional.yml index c7c2c8cc..b2969fd8 100644 --- a/.github/workflows/functional.yml +++ b/.github/workflows/functional.yml @@ -15,10 +15,15 @@ env: KEYSTONE_URL: http://localhost:8080 OS_KEYSTONE_CONFIG_DIR: ${{ github.workspace }}/etc +defaults: + run: + shell: bash + jobs: build: runs-on: ubuntu-latest steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Enable cache @@ -40,13 +45,29 @@ jobs: - name: Build Keystone run: cargo build + - name: Move artifacts to the root + run: mv target/debug/keystone* ./ + + - uses: taiki-e/install-action@v2 + with: + tool: just + + - name: Setup OPA + uses: open-policy-agent/setup-opa@v2 + with: + version: latest + + - name: Build policies + run: just build-policy + - name: Upload built binaries uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: keystone path: | - target/debug/keystone - target/debug/keystone-db + keystone + keystone-db + policy.wasm interop: runs-on: ubuntu-latest @@ -84,7 +105,7 @@ jobs: with: toolchain: stable - - name: Fetch pre-built keystone + - name: Fetch pre-built artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: keystone @@ -224,7 +245,7 @@ jobs: with: python-version: '3.12' - - name: Fetch pre-built keystone + - name: Fetch pre-built artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: keystone diff --git a/.gitignore b/.gitignore index bc747119..0776d79e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ **/target # Ignore rust files in the root folder *.rs +# no OpenPolicyAgent data +bundle.tar.gz +./*.rego +policy.wasm +.manifest doc/html diff --git a/Cargo.lock b/Cargo.lock index 4b60dd8b..f5bcbc3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "arbitrary" version = "1.4.1" @@ -186,7 +192,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] @@ -198,7 +204,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -236,9 +242,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.24" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d615619615a650c571269c00dca41db04b9210037fa76ed8239f70404ab56985" +checksum = "40f6024f3f856663b45fd0c9b6f2024034a702f453549449e0d84a305900dad4" dependencies = [ "brotli", "flate2", @@ -293,7 +299,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 1.0.7", "slab", "tracing", "windows-sys 0.59.0", @@ -356,7 +362,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -373,7 +379,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -393,9 +399,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" @@ -460,7 +466,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -613,7 +619,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -639,9 +645,12 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.18.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytecheck" @@ -721,6 +730,36 @@ dependencies = [ "windows-link", ] +[[package]] +name = "chrono-tz" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402" +dependencies = [ + "parse-zoneinfo", + "phf_codegen", +] + +[[package]] +name = "chronoutil" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b58b07a67cadda9502b270eca5e0f1cd3afd08445e0ab1d52d909db01b4543" +dependencies = [ + "chrono", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -789,7 +828,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -798,6 +837,12 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" + [[package]] name = "color-eyre" version = "0.6.5" @@ -956,6 +1001,132 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-assembler-x64" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4b56ebe316895d3fa37775d0a87b0c889cc933f5c8b253dbcc7c7bcb7fe7e4" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95cabbc01dfbd7dcd6c329ca44f0212910309c221797ac736a67a5bc8857fe1b" + +[[package]] +name = "cranelift-bforest" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ffe46df300a45f1dc6f609dc808ce963f0e3a2e971682c479a2d13e3b9b8ef" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-bitset" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b265bed7c51e1921fdae6419791d31af77d33662ee56d7b0fa0704dc8d231cab" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-codegen" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e606230a7e3a6897d603761baee0d19f88d077f17b996bb5089488a29ae96e41" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.15.4", + "log", + "pulley-interpreter", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a63bffafc23bc60969ad528e138788495999d935f0adcfd6543cb151ca8637d" +dependencies = [ + "cranelift-assembler-x64", + "cranelift-codegen-shared", + "pulley-interpreter", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af50281b67324b58e843170a6a5943cf6d387c06f7eeacc9f5696e4ab7ae7d7e" + +[[package]] +name = "cranelift-control" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c20c1b38d1abfbcebb0032e497e71156c0e3b8dcb3f0a92b9863b7bcaec290c" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2c67d95507c51b4a1ff3f3555fe4bfec36b9e13c1b684ccc602736f5d5f4a2" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", +] + +[[package]] +name = "cranelift-frontend" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e002691cc69c38b54fc7ec93e5be5b744f627d027031d991cc845d1d512d0ce" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93588ed1796cbcb0e2ad160403509e2c5d330d80dd6e0014ac6774c7ebac496" + +[[package]] +name = "cranelift-native" +version = "0.118.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5b09bdd6407bf5d89661b80cf926ce731c9e8cc184bf49102267a2369a8358e" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + [[package]] name = "crc" version = "3.3.0" @@ -1050,9 +1221,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -1100,7 +1271,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1124,7 +1295,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1135,7 +1306,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1187,7 +1358,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1208,7 +1379,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1218,7 +1389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1241,7 +1412,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -1265,6 +1436,17 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "duration-str" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb333721800c025e363e902b293040778f8ac79913db4f013abf1f1d7d382fd7" +dependencies = [ + "rust_decimal", + "thiserror 2.0.12", + "winnow", +] + [[package]] name = "dyn-clone" version = "1.0.19" @@ -1339,6 +1521,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1356,12 +1550,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1412,6 +1606,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fastrand" version = "2.3.0" @@ -1588,6 +1788,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1608,6 +1819,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1659,6 +1871,11 @@ name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +dependencies = [ + "fallible-iterator", + "indexmap 2.9.0", + "stable_deref_trait", +] [[package]] name = "glob" @@ -1748,6 +1965,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash", + "serde", ] [[package]] @@ -2046,6 +2264,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + [[package]] name = "ident_case" version = "1.0.1" @@ -2109,7 +2333,7 @@ checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2152,6 +2376,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -2187,6 +2420,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "159294d661a039f7644cea7e4d844e6b25aaf71c1ffe9d73a96d768c24b0faf4" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "json5" version = "0.4.1" @@ -2198,6 +2443,16 @@ dependencies = [ "serde", ] +[[package]] +name = "jsonptr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "keycloak" version = "26.2.300" @@ -2230,11 +2485,17 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.173" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libm" @@ -2261,6 +2522,12 @@ dependencies = [ "zlib-rs", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.9.4" @@ -2298,6 +2565,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -2329,6 +2605,15 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "memfd" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" +dependencies = [ + "rustix 0.38.44", +] + [[package]] name = "mime" version = "0.3.17" @@ -2394,7 +2679,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2406,7 +2691,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2535,6 +2820,9 @@ version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ + "crc32fast", + "hashbrown 0.15.4", + "indexmap 2.9.0", "memchr", ] @@ -2565,6 +2853,42 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "opa-wasm" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c07ec35ceaacb13349669e772705036975bde72b612e72b26a6bd6a71d909" +dependencies = [ + "anyhow", + "base64 0.22.1", + "chrono", + "chrono-tz", + "chronoutil", + "digest", + "duration-str", + "form_urlencoded", + "hex", + "hmac", + "json-patch", + "md-5", + "parse-size", + "rand 0.8.5", + "rayon", + "semver", + "serde", + "serde_json", + "serde_yaml", + "sha1", + "sha2", + "sprintf", + "thiserror 2.0.12", + "tokio", + "tracing", + "urlencoding", + "version_check", + "wasmtime", +] + [[package]] name = "openidconnect" version = "4.0.0" @@ -2619,7 +2943,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2659,16 +2983,19 @@ dependencies = [ "dyn-clone", "eyre", "fernet", + "futures-util", "http-body-util", "hyper", "hyper-util", "keycloak", "mockall", "mockall_double", + "opa-wasm", "openidconnect", "regex", "reqwest", "rmp", + "schemars 1.0.1", "sea-orm", "sea-orm-migration", "serde", @@ -2742,7 +3069,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2753,9 +3080,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "4.2.1" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" [[package]] name = "p256" @@ -2810,6 +3137,21 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parse-size" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b" + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.15" @@ -2868,7 +3210,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -2890,6 +3232,44 @@ dependencies = [ "serde", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2978,11 +3358,23 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 1.0.7", "tracing", "windows-sys 0.59.0", ] +[[package]] +name = "postcard" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.2" @@ -3070,7 +3462,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3090,16 +3482,25 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "version_check", "yansi", ] [[package]] -name = "ptr_meta" -version = "0.1.4" +name = "psm" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f" +dependencies = [ + "cc", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ "ptr_meta_derive", ] @@ -3115,6 +3516,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "pulley-interpreter" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3325791708ad50580aeacfcce06cb5e462c9ba7a2368e109cb2012b944b70e" +dependencies = [ + "cranelift-bitset", + "log", + "wasmtime-math", +] + [[package]] name = "quinn" version = "0.11.8" @@ -3158,9 +3570,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e529991f949c5e25755532370b8af5d114acae52326361d68d47af64aa842" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases", "libc", @@ -3181,9 +3593,9 @@ dependencies = [ [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "radium" @@ -3296,7 +3708,21 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", +] + +[[package]] +name = "regalloc2" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.4", + "log", + "rustc-hash", + "smallvec", ] [[package]] @@ -3512,7 +3938,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.103", + "syn 2.0.104", "walkdir", ] @@ -3583,6 +4009,19 @@ dependencies = [ "nom", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.0.7" @@ -3592,7 +4031,7 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -3673,6 +4112,31 @@ dependencies = [ "serde_json", ] +[[package]] +name = "schemars" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ca9fcb757952f8e8629b9ab066fc62da523c46c2b247b1708a3be06dd82530b" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.104", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3689,7 +4153,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3747,7 +4211,7 @@ dependencies = [ "proc-macro2", "quote", "sea-bae", - "syn 2.0.103", + "syn 2.0.104", "unicode-ident", ] @@ -3810,7 +4274,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "thiserror 2.0.12", ] @@ -3834,7 +4298,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -3932,7 +4396,18 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -3975,7 +4450,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4010,7 +4485,7 @@ dependencies = [ "hex", "indexmap 1.9.3", "indexmap 2.9.0", - "schemars", + "schemars 0.9.0", "serde", "serde_derive", "serde_json", @@ -4027,7 +4502,20 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.9.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", ] [[package]] @@ -4098,6 +4586,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.10" @@ -4142,6 +4636,21 @@ dependencies = [ "der", ] +[[package]] +name = "sprintf" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78222247fc55e10208ed1ba60f8296390bc67a489bc27a36231765d8d6f60ec5" +dependencies = [ + "thiserror 2.0.12", +] + +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "sqlx" version = "0.8.6" @@ -4204,7 +4713,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4227,7 +4736,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.103", + "syn 2.0.104", "tokio", "url", ] @@ -4412,9 +4921,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.103" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -4438,7 +4947,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4468,6 +4977,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + [[package]] name = "tempfile" version = "3.20.0" @@ -4477,10 +4992,19 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix", + "rustix 1.0.7", "windows-sys 0.59.0", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "termtree" version = "0.5.1" @@ -4523,7 +5047,7 @@ checksum = "5cf0ffc3ba4368e99597bd6afd83f4ff6febad66d9ae541ab46e697d32285fc0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4552,7 +5076,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4563,7 +5087,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4675,7 +5199,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4822,13 +5346,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -4898,7 +5422,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.103", + "syn 2.0.104", +] + +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] @@ -4970,6 +5505,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -4988,6 +5529,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -5034,7 +5581,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5159,7 +5706,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -5194,7 +5741,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5208,6 +5755,230 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.226.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d81b727619aec227dce83e7f7420d4e56c79acd044642a356ea045b98d4e13" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.226.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc28600dcb2ba68d7e5f1c3ba4195c2bddc918c0243fd702d0b6dbd05689b681" +dependencies = [ + "bitflags", + "hashbrown 0.15.4", + "indexmap 2.9.0", + "semver", + "serde", +] + +[[package]] +name = "wasmprinter" +version = "0.226.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "753a0516fa6c01756ee861f36878dfd9875f273aea9409d9ea390a333c5bcdc2" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser", +] + +[[package]] +name = "wasmtime" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fe78033c72da8741e724d763daf1375c93a38bfcea99c873ee4415f6098c3f" +dependencies = [ + "addr2line", + "anyhow", + "async-trait", + "bitflags", + "bumpalo", + "cc", + "cfg-if", + "hashbrown 0.15.4", + "indexmap 2.9.0", + "libc", + "log", + "mach2", + "memfd", + "object", + "once_cell", + "paste", + "postcard", + "psm", + "pulley-interpreter", + "rayon", + "rustix 0.38.44", + "serde", + "serde_derive", + "smallvec", + "sptr", + "target-lexicon", + "trait-variant", + "wasmparser", + "wasmtime-asm-macros", + "wasmtime-component-macro", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-icache-coherence", + "wasmtime-math", + "wasmtime-slab", + "wasmtime-versioned-export-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f3d44ae977d70ccf80938b371d5ec60b6adedf60800b9e8dd1223bb69f4cbc" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-component-macro" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397e68ee29eb072d8d8741c9d2c971a284cd1bc960ebf2c1f6a33ea6ba16d6e1" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasmtime-component-util", + "wasmtime-wit-bindgen", + "wit-parser", +] + +[[package]] +name = "wasmtime-component-util" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f292ef5eb2cf3d414c2bde59c7fa0feeba799c8db9a8c5a656ad1d1a1d05e10b" + +[[package]] +name = "wasmtime-cranelift" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fc12eb8ea695a30007a4849a5fd56209dd86a15579e92e0c27c27122818505" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools 0.12.1", + "log", + "object", + "pulley-interpreter", + "smallvec", + "target-lexicon", + "thiserror 1.0.69", + "wasmparser", + "wasmtime-environ", + "wasmtime-versioned-export-macros", +] + +[[package]] +name = "wasmtime-environ" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b6b4bf08e371edf262cccb62de10e214bd4aaafaa069f1cd49c9c1c3a5ae8e4" +dependencies = [ + "anyhow", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "indexmap 2.9.0", + "log", + "object", + "postcard", + "serde", + "serde_derive", + "smallvec", + "target-lexicon", + "wasm-encoder", + "wasmparser", + "wasmprinter", +] + +[[package]] +name = "wasmtime-fiber" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8828d7d8fbe90d087a9edea9223315caf7eb434848896667e5d27889f1173" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "rustix 0.38.44", + "wasmtime-asm-macros", + "wasmtime-versioned-export-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54f6c6c7e9d7eeee32dfcc10db7f29d505ee7dd28d00593ea241d5f70698e64" +dependencies = [ + "anyhow", + "cfg-if", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasmtime-math" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1108aad2e6965698f9207ea79b80eda2b3dcc57dcb69f4258296d4664ae32cd" +dependencies = [ + "libm", +] + +[[package]] +name = "wasmtime-slab" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d6a321317281b721c5530ef733e8596ecc6065035f286ccd155b3fa8e0ab2f" + +[[package]] +name = "wasmtime-versioned-export-macros" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5732a5c86efce7bca121a61d8c07875f6b85c1607aa86753b40f7f8bd9d3a780" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "wasmtime-wit-bindgen" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505c13fa0cac6c43e805347acf1e916c8de54e3790f2c22873c5692964b09b62" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.9.0", + "wit-parser", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -5297,9 +6068,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] @@ -5366,7 +6137,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5377,7 +6148,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5388,9 +6159,9 @@ checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-registry" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link", "windows-result", @@ -5442,6 +6213,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -5466,13 +6246,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5485,6 +6281,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5497,6 +6299,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5509,12 +6317,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5527,6 +6347,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5539,6 +6365,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5551,6 +6383,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5563,6 +6401,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.11" @@ -5581,6 +6425,24 @@ dependencies = [ "bitflags", ] +[[package]] +name = "wit-parser" +version = "0.226.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f007722bfd43a2978c5b8b90f02c927dddf0f11c5f5b50929816b3358718cd" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.9.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "writeable" version = "0.6.1" @@ -5650,28 +6512,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5691,7 +6553,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", "synstructure", ] @@ -5712,7 +6574,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] @@ -5745,7 +6607,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.103", + "syn 2.0.104", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3548e9c3..e857cbac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,10 +35,13 @@ derive_builder = { version = "0.20" } dyn-clone = { version = "1.0" } eyre = { version = "0.6" } fernet = { version = "0.2" } +futures-util = { version = "0.3" } mockall_double = { version = "0.3" } +opa-wasm = { version = "^0.1" } openidconnect = { version = "4.0" } regex = { version = "1.11"} rmp = { version = "0.8" } +schemars = { version = "1.0" } sea-orm = { version = "1.1", features = ["sqlx-mysql", "sqlx-postgres", "runtime-tokio"] } sea-orm-migration = { version = "1.1" } serde = { version = "1.0" } @@ -61,17 +64,17 @@ webauthn-rs = { version = "0.5", features = ["danger-allow-state-serialisation"] [dev-dependencies] criterion = { version = "0.6", features = ["async_tokio"] } http-body-util = "0.1" -hyper = { version = "1.6.0", features = ["http1"] } -hyper-util = { version = "0.1.14", features = ["tokio", "http1"] } -keycloak = "26.2.300" +hyper = { version = "1.6", features = ["http1"] } +hyper-util = { version = "0.1", features = ["tokio", "http1"] } +keycloak = { version = "26.2" } mockall = { version = "0.13" } -reqwest = { version = "0.12.20", features = ["json"] } +reqwest = { version = "0.12", features = ["json"] } sea-orm = { version = "1.1", features = ["mock"]} -serde_urlencoded = "0.7.1" +serde_urlencoded = { version = "0.7" } tempfile = { version = "3.20" } thirtyfour = "0.36.0" tracing-test = { version = "0.2" } -url = "2.5" +url = { version = "2.5" } [profile.release] strip = true diff --git a/deny.toml b/deny.toml index dee31787..cefcd775 100644 --- a/deny.toml +++ b/deny.toml @@ -93,6 +93,7 @@ ignore = [ # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", "BSD-3-Clause", "BSL-1.0", "CC0-1.0", diff --git a/justfile b/justfile new file mode 100644 index 00000000..c72edb84 --- /dev/null +++ b/justfile @@ -0,0 +1,7 @@ +POLICY_ENTRY_POINTS := "-e identity/identity_provider_list" + +[working-directory: 'policy'] +@build-policy: + echo "Building policy" + @opa build -t wasm {{POLICY_ENTRY_POINTS}} . + @tar xvf bundle.tar.gz -C ../ /policy.wasm diff --git a/policy/identity_provider_list.rego b/policy/identity_provider_list.rego new file mode 100644 index 00000000..3d9f3f4e --- /dev/null +++ b/policy/identity_provider_list.rego @@ -0,0 +1,30 @@ +package identity.identity_provider_list + +# List identity providers. + +default allow := false + +allow if { + count(violation) == 0 +} + +violation contains {"field": "domain_id", "msg": "only admin user is allowed to list identity providers not owned by the domain in scope."} if { + not "admin" in input.credentials.roles + not global_or_local_idp +} + +global_idp if { + not input.target.domain_id +} + +local_idp if { + input.target.domain_id == input.credentials.domain_id +} + +global_or_local_idp if { + global_idp +} + +global_or_local_idp if { + local_idp +} diff --git a/src/api/auth.rs b/src/api/auth.rs index aca5de8f..8156bfc0 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -14,10 +14,11 @@ use axum::{ extract::{FromRef, FromRequestParts}, - http::{StatusCode, request::Parts}, + http::request::Parts, }; use std::sync::Arc; +use crate::api::KeystoneApiError; use crate::keystone::ServiceState; use crate::token::{Token, TokenApi}; @@ -29,7 +30,7 @@ where ServiceState: FromRef, S: Send + Sync, { - type Rejection = (StatusCode, &'static str); + type Rejection = KeystoneApiError; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let auth_header = parts @@ -40,18 +41,25 @@ where let auth_header = if let Some(auth_header) = auth_header { auth_header } else { - return Err((StatusCode::UNAUTHORIZED, "not authorized")); + return Err(KeystoneApiError::Unauthorized)?; }; let state = Arc::from_ref(state); - Ok(Self( - state - .provider - .get_token_provider() - .validate_token(auth_header, Some(false), None) - .await - .map_err(|_| (StatusCode::UNAUTHORIZED, "not authorized"))?, - )) + let token = state + .provider + .get_token_provider() + .validate_token(auth_header, Some(false), None) + .await + .map_err(|_| KeystoneApiError::Unauthorized)?; + + // Expand the information (user, project, roles, etc) about the user when a token is valid + let token = state + .provider + .get_token_provider() + .expand_token_information(&token, &state.db, &state.provider) + .await?; + + Ok(Self(token)) } } diff --git a/src/api/common.rs b/src/api/common.rs index 86389bea..920ee46e 100644 --- a/src/api/common.rs +++ b/src/api/common.rs @@ -128,6 +128,7 @@ mod tests { use crate::config::Config; use crate::keystone::Service; + use crate::policy::MockPolicyFactory; use crate::provider::Provider; use crate::resource::{MockResourceProvider, types::Domain}; @@ -164,6 +165,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ); diff --git a/src/api/error.rs b/src/api/error.rs index a1d0fc1c..7790f649 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -27,6 +27,7 @@ use crate::auth::AuthenticationError; use crate::catalog::error::CatalogProviderError; use crate::federation::error::FederationProviderError; use crate::identity::error::IdentityProviderError; +use crate::policy::PolicyError; use crate::resource::error::ResourceProviderError; use crate::token::error::TokenProviderError; @@ -106,6 +107,12 @@ pub enum KeystoneApiError { source: IdentityProviderError, }, + #[error(transparent)] + Policy { + #[from] + source: PolicyError, + }, + #[error(transparent)] ResourceError { #[from] diff --git a/src/api/v3/auth/token/common.rs b/src/api/v3/auth/token/common.rs index 569bbf56..449ea7d8 100644 --- a/src/api/v3/auth/token/common.rs +++ b/src/api/v3/auth/token/common.rs @@ -186,6 +186,7 @@ mod tests { use crate::config::Config; use crate::identity::{MockIdentityProvider, types::UserResponse}; use crate::keystone::Service; + use crate::policy::MockPolicyFactory; use crate::provider::Provider; use crate::resource::{ MockResourceProvider, @@ -230,6 +231,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ); @@ -283,6 +285,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ); @@ -364,6 +367,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ); diff --git a/src/api/v3/auth/token/mod.rs b/src/api/v3/auth/token/mod.rs index e186b66c..e27310a6 100644 --- a/src/api/v3/auth/token/mod.rs +++ b/src/api/v3/auth/token/mod.rs @@ -283,6 +283,7 @@ mod tests { types::{UserPasswordAuthRequest, UserResponse}, }; use crate::keystone::Service; + use crate::policy::MockPolicyFactory; use crate::provider::Provider; use crate::resource::{ MockResourceProvider, @@ -326,8 +327,15 @@ mod tests { .build() .unwrap(); - let state = - Arc::new(Service::new(config, DatabaseConnection::Disconnected, provider).unwrap()); + let state = Arc::new( + Service::new( + config, + DatabaseConnection::Disconnected, + provider, + MockPolicyFactory::new(), + ) + .unwrap(), + ); assert_eq!( auth_info, @@ -389,8 +397,15 @@ mod tests { .build() .unwrap(); - let state = - Arc::new(Service::new(config, DatabaseConnection::Disconnected, provider).unwrap()); + let state = Arc::new( + Service::new( + config, + DatabaseConnection::Disconnected, + provider, + MockPolicyFactory::new(), + ) + .unwrap(), + ); assert_eq!( AuthenticatedInfo::builder() @@ -432,8 +447,15 @@ mod tests { .build() .unwrap(); - let state = - Arc::new(Service::new(config, DatabaseConnection::Disconnected, provider).unwrap()); + let state = Arc::new( + Service::new( + config, + DatabaseConnection::Disconnected, + provider, + MockPolicyFactory::new(), + ) + .unwrap(), + ); let rsp = authenticate_request( &state, @@ -512,6 +534,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ); @@ -624,6 +647,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ); @@ -660,9 +684,18 @@ mod tests { ..Default::default() })) }); + token_mock + .expect_expand_token_information() + .withf(|token: &ProviderToken, &_, &_| token.user_id() == "bar") + .returning(|_, _, _| { + Ok(ProviderToken::Unscoped(UnscopedPayload { + user_id: "foo".into(), + ..Default::default() + })) + }); token_mock .expect_validate_token() - .withf(|token: &'_ str, _, _| token == "bar") + .withf(|token: &'_ str, _, _| token == "baz") .returning(|_, _, _| Err(TokenProviderError::Expired)); let provider = Provider::mocked_builder() @@ -675,6 +708,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ); @@ -689,7 +723,7 @@ mod tests { Request::builder() .uri("/") .header("x-auth-token", "foo") - .header("x-subject-token", "bar") + .header("x-subject-token", "baz") .body(Body::empty()) .unwrap(), ) @@ -831,8 +865,15 @@ mod tests { .build() .unwrap(); - let state = - Arc::new(Service::new(config, DatabaseConnection::Disconnected, provider).unwrap()); + let state = Arc::new( + Service::new( + config, + DatabaseConnection::Disconnected, + provider, + MockPolicyFactory::new(), + ) + .unwrap(), + ); let mut api = openapi_router() .layer(TraceLayer::new_for_http()) @@ -927,8 +968,15 @@ mod tests { .build() .unwrap(); - let state = - Arc::new(Service::new(config, DatabaseConnection::Disconnected, provider).unwrap()); + let state = Arc::new( + Service::new( + config, + DatabaseConnection::Disconnected, + provider, + MockPolicyFactory::new(), + ) + .unwrap(), + ); let mut api = openapi_router() .layer(TraceLayer::new_for_http()) diff --git a/src/api/v3/federation/identity_provider.rs b/src/api/v3/federation/identity_provider.rs index ea5389b2..6abf3c6d 100644 --- a/src/api/v3/federation/identity_provider.rs +++ b/src/api/v3/federation/identity_provider.rs @@ -18,6 +18,7 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use mockall_double::double; use utoipa_axum::{router::OpenApiRouter, routes}; use crate::api::auth::Auth; @@ -25,6 +26,8 @@ use crate::api::error::KeystoneApiError; use crate::api::v3::federation::types::*; use crate::federation::FederationApi; use crate::keystone::ServiceState; +#[double] +use crate::policy::Policy; pub(super) fn openapi_router() -> OpenApiRouter { OpenApiRouter::new() @@ -47,14 +50,22 @@ pub(super) fn openapi_router() -> OpenApiRouter { #[tracing::instrument( name = "api::identity_provider_list", level = "debug", - skip(state, _user_auth), + skip(state, _user_auth, policy), err(Debug) )] async fn list( Auth(_user_auth): Auth, + mut policy: Policy, Query(query): Query, State(state): State, ) -> Result { + policy + .enforce( + "identity/identity_provider_list", + &_user_auth, + serde_json::to_value(&query)?, + ) + .await?; let identity_providers: Vec = state .provider .get_federation_provider() @@ -218,6 +229,7 @@ mod tests { MockFederationProvider, error::FederationProviderError, types as provider_types, }; use crate::keystone::{Service, ServiceState}; + use crate::policy::{MockPolicy, MockPolicyFactory, PolicyEvaluationResult}; use crate::provider::Provider; use crate::token::{MockTokenProvider, Token, UnscopedPayload}; @@ -229,6 +241,14 @@ mod tests { ..Default::default() })) }); + token_mock + .expect_expand_token_information() + .returning(|_, _, _| { + Ok(Token::Unscoped(UnscopedPayload { + user_id: "bar".into(), + ..Default::default() + })) + }); let provider = Provider::mocked_builder() .federation(federation_mock) @@ -236,11 +256,20 @@ mod tests { .build() .unwrap(); + let mut policy_factory_mock = MockPolicyFactory::default(); + policy_factory_mock.expect_instantiate().returning(|| { + let mut policy_mock = MockPolicy::default(); + policy_mock + .expect_enforce() + .returning(|_, _, _| Ok(PolicyEvaluationResult::allowed())); + Ok(policy_mock) + }); Arc::new( Service::new( Config::default(), DatabaseConnection::Disconnected, provider, + policy_factory_mock, ) .unwrap(), ) @@ -263,7 +292,6 @@ mod tests { ..Default::default() }]) }); - let state = get_mocked_state(federation_mock); let mut api = openapi_router() diff --git a/src/api/v3/federation/mapping.rs b/src/api/v3/federation/mapping.rs index d7ae8659..e7f08ea7 100644 --- a/src/api/v3/federation/mapping.rs +++ b/src/api/v3/federation/mapping.rs @@ -204,6 +204,7 @@ mod tests { MockFederationProvider, error::FederationProviderError, types as provider_types, }; use crate::keystone::{Service, ServiceState}; + use crate::policy::{MockPolicy, MockPolicyFactory, PolicyEvaluationResult}; use crate::provider::Provider; use crate::token::{MockTokenProvider, Token, UnscopedPayload}; @@ -215,6 +216,14 @@ mod tests { ..Default::default() })) }); + token_mock + .expect_expand_token_information() + .returning(|_, _, _| { + Ok(Token::Unscoped(UnscopedPayload { + user_id: "bar".into(), + ..Default::default() + })) + }); let provider = Provider::mocked_builder() .federation(federation_mock) @@ -222,11 +231,21 @@ mod tests { .build() .unwrap(); + let mut policy_factory_mock = MockPolicyFactory::default(); + policy_factory_mock.expect_instantiate().returning(|| { + let mut policy_mock = MockPolicy::default(); + policy_mock + .expect_enforce() + .returning(|_, _, _| Ok(PolicyEvaluationResult::allowed())); + Ok(policy_mock) + }); + Arc::new( Service::new( Config::default(), DatabaseConnection::Disconnected, provider, + policy_factory_mock, ) .unwrap(), ) diff --git a/src/api/v3/role/mod.rs b/src/api/v3/role/mod.rs index e43c4419..babd36cd 100644 --- a/src/api/v3/role/mod.rs +++ b/src/api/v3/role/mod.rs @@ -120,6 +120,7 @@ mod tests { use crate::config::Config; use crate::keystone::{Service, ServiceState}; + use crate::policy::MockPolicyFactory; use crate::provider::Provider; use crate::token::{MockTokenProvider, Token, UnscopedPayload}; @@ -134,6 +135,14 @@ mod tests { ..Default::default() })) }); + token_mock + .expect_expand_token_information() + .returning(|_, _, _| { + Ok(Token::Unscoped(UnscopedPayload { + user_id: "bar".into(), + ..Default::default() + })) + }); let provider = Provider::mocked_builder() .assignment(assignment_mock) @@ -146,6 +155,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ) diff --git a/src/api/v3/role_assignment/mod.rs b/src/api/v3/role_assignment/mod.rs index da04c1d6..b2992177 100644 --- a/src/api/v3/role_assignment/mod.rs +++ b/src/api/v3/role_assignment/mod.rs @@ -92,6 +92,7 @@ mod tests { use crate::config::Config; use crate::keystone::{Service, ServiceState}; + use crate::policy::MockPolicyFactory; use crate::provider::Provider; use crate::token::{MockTokenProvider, Token, UnscopedPayload}; @@ -104,6 +105,14 @@ mod tests { ..Default::default() })) }); + token_mock + .expect_expand_token_information() + .returning(|_, _, _| { + Ok(Token::Unscoped(UnscopedPayload { + user_id: "bar".into(), + ..Default::default() + })) + }); let provider = Provider::mocked_builder() .assignment(assignment_mock) @@ -116,6 +125,7 @@ mod tests { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::new(), ) .unwrap(), ) diff --git a/src/bin/keystone.rs b/src/bin/keystone.rs index cddaaa1a..229ec440 100644 --- a/src/bin/keystone.rs +++ b/src/bin/keystone.rs @@ -30,7 +30,7 @@ use tower_http::{ trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}, }; use tracing::{Level, error, info, info_span, trace}; -use tracing_subscriber::{filter::LevelFilter, prelude::*}; +use tracing_subscriber::{filter::*, prelude::*}; use utoipa::OpenApi; use utoipa_axum::router::OpenApiRouter; use utoipa_swagger_ui::SwaggerUi; @@ -41,6 +41,7 @@ use openstack_keystone::config::Config; use openstack_keystone::federation::FederationApi; use openstack_keystone::keystone::{Service, ServiceState}; use openstack_keystone::plugin_manager::PluginManager; +use openstack_keystone::policy::PolicyFactory; use openstack_keystone::provider::Provider; /// Simple program to greet a person @@ -74,14 +75,21 @@ impl MakeRequestId for OpenStackRequestId { async fn main() -> Result<(), Report> { let args = Args::parse(); - let log_layer = tracing_subscriber::fmt::layer() - .with_writer(io::stderr) - .with_filter(match args.verbose { + let filter = Targets::new() + .with_default(match args.verbose { 0 => LevelFilter::WARN, 1 => LevelFilter::INFO, 2 => LevelFilter::DEBUG, _ => LevelFilter::TRACE, - }); + }) + .with_target("cranelift_codegen", Level::INFO) + .with_target("wasmtime_codegen", Level::INFO) + .with_target("wasmtime_cranelift", Level::INFO) + .with_target("wasmtime::runtime", Level::INFO); + + let log_layer = tracing_subscriber::fmt::layer() + .with_writer(io::stderr) + .with_filter(filter); // build the tracing registry tracing_subscriber::registry().with(log_layer).init(); @@ -104,7 +112,10 @@ async fn main() -> Result<(), Report> { let provider = Provider::new(cfg.clone(), plugin_manager)?; - let shared_state = Arc::new(Service::new(cfg, conn, provider).unwrap()); + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("policy.wasm"); + let policy = PolicyFactory::from_wasm(&path).await?; + + let shared_state = Arc::new(Service::new(cfg, conn, provider, policy)?); spawn(cleanup(cloned_token, shared_state.clone())); diff --git a/src/config.rs b/src/config.rs index 4afe5e63..17e32102 100644 --- a/src/config.rs +++ b/src/config.rs @@ -52,6 +52,10 @@ pub struct Config { #[serde(default)] pub identity: IdentitySection, + /// API policy enforcement + #[serde(default)] + pub api_policy: PolicySection, + /// Resource provider related configuration #[serde(default)] pub resource: ResourceSection, @@ -112,6 +116,13 @@ impl DatabaseSection { } } +/// The configuration options for the API policy enforcement. +#[derive(Clone, Debug, Default, Deserialize)] +pub struct PolicySection { + /// Whether the policy enforcement should be enforced or not. + pub enable: bool, +} + #[derive(Debug, Default, Deserialize, Clone)] pub struct AssignmentSection { pub driver: String, @@ -197,6 +208,7 @@ impl Config { let mut builder = config::Config::builder(); builder = builder + .set_default("api_policy.enable", "true")? .set_default("identity.max_password_length", "4096")? .set_default("fernet_tokens.key_repository", "/etc/keystone/fernet-keys/")? .set_default("fernet_tokens.max_active_keys", "3")? diff --git a/src/error.rs b/src/error.rs index 6c975ccd..8a4a759b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,6 +18,7 @@ use crate::assignment::error::*; use crate::catalog::error::*; use crate::federation::error::*; use crate::identity::error::*; +use crate::policy::*; use crate::resource::error::*; use crate::token::TokenProviderError; @@ -47,6 +48,18 @@ pub enum KeystoneError { source: IdentityProviderError, }, + #[error(transparent)] + IO { + #[from] + source: std::io::Error, + }, + + #[error(transparent)] + Policy { + #[from] + source: PolicyError, + }, + #[error(transparent)] ResourceError { #[from] diff --git a/src/keystone.rs b/src/keystone.rs index 1bb206ff..5c44cec7 100644 --- a/src/keystone.rs +++ b/src/keystone.rs @@ -12,14 +12,20 @@ // // SPDX-License-Identifier: Apache-2.0 -use axum::extract::FromRef; +use axum::extract::{FromRef, FromRequestParts}; +use mockall_double::double; use sea_orm::DatabaseConnection; use std::sync::Arc; use tracing::info; use webauthn_rs::{Webauthn, WebauthnBuilder, prelude::Url}; +use crate::api::error::KeystoneApiError; use crate::config::Config; use crate::error::KeystoneError; +#[double] +use crate::policy::Policy; +#[double] +use crate::policy::PolicyFactory; use crate::provider::Provider; // Placing ServiceState behind Arc is necessary to address DatabaseConnection not implementing @@ -27,12 +33,21 @@ use crate::provider::Provider; //#[derive(Clone)] #[derive(FromRef)] pub struct Service { + /// Config file pub config: Config, + /// Service/resource Provider pub provider: Provider, + /// Database connection #[from_ref(skip)] pub db: DatabaseConnection, + + /// Policy factory + pub policy_factory: Arc, + + /// WebAuthN provider pub webauthn: Webauthn, + /// Shutdown flag pub shutdown: bool, } @@ -43,6 +58,7 @@ impl Service { cfg: Config, db: DatabaseConnection, provider: Provider, + policy_factory: PolicyFactory, ) -> Result { // Effective domain name. let rp_id = "localhost"; @@ -63,6 +79,7 @@ impl Service { config: cfg.clone(), provider, db, + policy_factory: Arc::new(policy_factory), webauthn, shutdown: false, }) @@ -73,3 +90,15 @@ impl Service { Ok(()) } } + +impl FromRequestParts for Policy { + type Rejection = KeystoneApiError; + + async fn from_request_parts( + _parts: &mut axum::http::request::Parts, + state: &ServiceState, + ) -> Result { + let policy = state.policy_factory.instantiate().await?; + Ok(policy) + } +} diff --git a/src/lib.rs b/src/lib.rs index 809688b4..b1d2b499 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub mod federation; pub mod identity; pub mod keystone; pub mod plugin_manager; +pub mod policy; pub mod provider; pub mod resource; pub mod token; diff --git a/src/policy.rs b/src/policy.rs new file mode 100644 index 00000000..67dad237 --- /dev/null +++ b/src/policy.rs @@ -0,0 +1,270 @@ +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] +use mockall::mock; +use opa_wasm::{ + Runtime, + wasmtime::{Config, Engine, Module, OptLevel, Store}, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; +use std::path::Path; +use thiserror::Error; +use tokio::io::AsyncRead; +use tokio::io::AsyncReadExt; +use tracing::debug; + +use crate::token::Token; + +#[derive(Debug, Error)] +pub enum PolicyError { + #[error("module compilation task crashed")] + Compilation(#[from] eyre::Report), + + #[error(transparent)] + IO(#[from] std::io::Error), + + #[error(transparent)] + Join(#[from] tokio::task::JoinError), + + #[error(transparent)] + Wasm(#[from] opa_wasm::wasmtime::Error), +} + +#[derive(Default)] +pub struct PolicyFactory { + engine: Option, + module: Option, +} + +impl PolicyFactory { + #[tracing::instrument(name = "policy.from_defaults", err)] + pub async fn from_defaults() -> Result { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("policy.wasm"); + let file = tokio::fs::File::open(path).await?; + PolicyFactory::load(file).await + } + + #[tracing::instrument(name = "policy.from_wasm", err)] + pub async fn from_wasm(path: &Path) -> Result { + let file = tokio::fs::File::open(path).await?; + PolicyFactory::load(file).await + } + + #[tracing::instrument(name = "policy.load", skip(source), err)] + pub async fn load( + mut source: impl AsyncRead + std::marker::Unpin, + ) -> Result { + let mut config = Config::default(); + config.async_support(true); + config.cranelift_opt_level(OptLevel::SpeedAndSize); + + let engine = Engine::new(&config)?; + + // Read and compile the module + let mut buf = Vec::new(); + source.read_to_end(&mut buf).await?; + // Compilation is CPU-bound, so spawn that in a blocking task + let (engine, module) = tokio::task::spawn_blocking(move || { + let module = Module::new(&engine, buf).map_err(PolicyError::from)?; + Ok((engine, module)) + }) + .await? + .map_err(PolicyError::Compilation)?; + + let factory = Self { + engine: Some(engine), + module: Some(module), + }; + + // Try to instantiate + factory.instantiate().await?; + + Ok(factory) + } + + #[tracing::instrument(name = "policy.instantiate", skip_all, err)] + pub async fn instantiate(&self) -> Result { + if let (Some(engine), Some(module)) = (&self.engine, &self.module) { + let mut store = Store::new(engine, ()); + let runtime = Runtime::new(&mut store, module).await?; + + let instance = runtime.without_data(&mut store).await?; + Ok(Policy { + store: Some(store), + instance: Some(instance), + }) + } else { + Ok(Policy { + store: None, + instance: None, + }) + } + } +} + +#[cfg(test)] +mock! { + pub Policy { + pub async fn enforce( + &mut self, + policy_name: &str, + credentials: &Token, + target: Value, + ) -> Result; + } +} + +#[cfg(test)] +mock! { + pub PolicyFactory { + pub async fn instantiate(&self) -> Result; + } +} + +pub struct Policy { + store: Option>, + instance: Option>, +} + +#[derive(Debug, Error)] +#[error("failed to evaluate policy")] +pub enum EvaluationError { + Serialization(#[from] serde_json::Error), + Evaluation(#[from] eyre::Report), +} + +/// OpenPolicyAgent `Credentials` object +#[derive(Serialize)] +pub struct Credentials { + pub user_id: String, + pub roles: Vec, + pub project_id: Option, + pub domain_id: Option, +} + +impl From<&Token> for Credentials { + fn from(token: &Token) -> Self { + Self { + user_id: token.user_id().clone(), + roles: token + .roles() + .map(|x| x.iter().map(|role| role.name.clone()).collect::>()) + .unwrap_or_default(), + project_id: token.project().map(|val| val.id.clone()), + domain_id: token.domain().map(|val| val.id.clone()), + } + } +} + +impl Policy { + #[tracing::instrument( + name = "policy.evaluate", + skip_all, + fields( + entrypoint = policy_name.as_ref(), + ), + err, + )] + pub async fn enforce>( + &mut self, + policy_name: P, + credentials: &Token, + target: Value, + ) -> Result { + let input = json!({ + "credentials": Credentials::from(credentials), + "target": target + }); + + if let (Some(store), Some(instance)) = (&mut self.store, &self.instance) { + let [res]: [OpaResponse; 1] = instance + .evaluate(store, policy_name.as_ref(), &input) + .await?; + debug!("Res is {:?}", res); + + Ok(res.result) + } else { + debug!("not enforcing policy due to the absence of initialized WASM data"); + Ok(PolicyEvaluationResult { + allow: true, + violations: None, + }) + } + } +} + +/// A single violation of a policy. +#[derive(Deserialize, Debug, JsonSchema)] +pub struct Violation { + pub msg: String, + pub field: Option, +} + +/// The OpenPolicyAgent response. +#[derive(Deserialize, Debug)] +pub struct OpaResponse { + pub result: PolicyEvaluationResult, +} + +/// The result of a policy evaluation. +#[derive(Deserialize, Debug)] +pub struct PolicyEvaluationResult { + pub allow: bool, + #[serde(rename = "violation")] + pub violations: Option>, +} + +impl std::fmt::Display for PolicyEvaluationResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut first = true; + if let Some(violations) = &self.violations { + for violation in violations { + if first { + first = false; + } else { + write!(f, ", ")?; + } + write!(f, "{}", violation.msg)?; + } + } + Ok(()) + } +} + +impl PolicyEvaluationResult { + #[must_use] + pub fn allow(&self) -> bool { + self.allow + } + + /// Returns true if the policy evaluation was successful. + #[must_use] + pub fn valid(&self) -> bool { + self.violations + .as_deref() + .map(|x| x.is_empty()) + .unwrap_or(false) + } + + #[cfg(test)] + pub fn allowed() -> Self { + Self { + allow: true, + violations: None, + } + } +} diff --git a/src/tests/api.rs b/src/tests/api.rs index e752af06..f30b5d63 100644 --- a/src/tests/api.rs +++ b/src/tests/api.rs @@ -18,6 +18,7 @@ use std::sync::Arc; use crate::config::Config; use crate::identity::MockIdentityProvider; use crate::keystone::{Service, ServiceState}; +use crate::policy::{MockPolicy, MockPolicyFactory, PolicyEvaluationResult}; use crate::provider::Provider; use crate::token::{MockTokenProvider, Token, TokenProviderError, UnscopedPayload}; @@ -37,6 +38,7 @@ pub(crate) fn get_mocked_state_unauthed() -> ServiceState { Config::default(), DatabaseConnection::Disconnected, provider, + MockPolicyFactory::default(), ) .unwrap(), ) @@ -50,6 +52,14 @@ pub(crate) fn get_mocked_state(identity_mock: MockIdentityProvider) -> ServiceSt ..Default::default() })) }); + token_mock + .expect_expand_token_information() + .returning(|_, _, _| { + Ok(Token::Unscoped(UnscopedPayload { + user_id: "bar".into(), + ..Default::default() + })) + }); let provider = Provider::mocked_builder() .identity(identity_mock) @@ -57,11 +67,21 @@ pub(crate) fn get_mocked_state(identity_mock: MockIdentityProvider) -> ServiceSt .build() .unwrap(); + let mut policy_factory_mock = MockPolicyFactory::default(); + policy_factory_mock.expect_instantiate().returning(|| { + let mut policy_mock = MockPolicy::default(); + policy_mock + .expect_enforce() + .returning(|_, _, _| Ok(PolicyEvaluationResult::allowed())); + Ok(policy_mock) + }); + Arc::new( Service::new( Config::default(), DatabaseConnection::Disconnected, provider, + policy_factory_mock, ) .unwrap(), )