diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07008be3f44f..f02964436ff7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -390,7 +390,7 @@ jobs: run: | set -ex sudo apt-get update - sudo apt-get install -y ${{ matrix.gcc_package }} ninja-build + sudo apt-get install -y ${{ matrix.gcc_package }} ninja-build libssl-dev # Configure Cargo for cross compilation and tell it how it can run # cross executables diff --git a/Cargo.lock b/Cargo.lock index 9c012e0b60ae..a9c248a25fa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,22 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpp_demangle" version = "0.3.5" @@ -1273,6 +1289,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1303,6 +1334,95 @@ dependencies = [ "winapi", ] +[[package]] +name = "futures" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -1382,6 +1502,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.2" @@ -1444,6 +1583,40 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.1", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + [[package]] name = "humantime" version = "1.3.0" @@ -1459,6 +1632,43 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "hyper" +version = "0.14.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.1", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "id-arena" version = "2.2.1" @@ -1782,6 +1992,12 @@ dependencies = [ "autocfg 1.1.0", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1793,34 +2009,32 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "windows-sys", ] [[package]] -name = "ntapi" -version = "0.3.7" +name = "native-tls" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ - "winapi", + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1948,6 +2162,51 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg 1.1.0", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "openvino" version = "0.4.2" @@ -2015,7 +2274,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.6", ] [[package]] @@ -2032,6 +2301,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "paste" version = "1.0.7" @@ -2059,6 +2341,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkcs1" version = "0.2.4" @@ -2083,6 +2371,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "plotters" version = "0.3.1" @@ -2472,6 +2766,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "reqwest" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rsa" version = "0.5.0" @@ -2542,12 +2873,44 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.137" @@ -2589,6 +2952,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.1", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.9.9" @@ -2643,6 +3018,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "1.3.2" @@ -2659,6 +3043,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg 1.1.0", +] + [[package]] name = "slice-group-by" version = "0.3.0" @@ -2676,9 +3069,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -2912,19 +3305,22 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.18.4" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bfb875c82dc0a4f1f37a30e720dee181a2b3a06a428b0fc6873ea38d6407850" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" dependencies = [ + "autocfg 1.1.0", "bytes", "libc", "memchr", "mio", "num_cpus", + "parking_lot 0.12.1", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] @@ -2938,6 +3334,30 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.9" @@ -2947,6 +3367,12 @@ dependencies = [ "serde", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.34" @@ -2992,6 +3418,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + [[package]] name = "typenum" version = "1.15.0" @@ -3083,6 +3515,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -3109,6 +3547,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3184,7 +3632,7 @@ dependencies = [ "hmac", "k256", "p256", - "parking_lot", + "parking_lot 0.11.2", "pqcrypto", "rand_core 0.5.1", "rand_core 0.6.3", @@ -3197,6 +3645,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "wasi-experimental-http-wasmtime" +version = "0.10.0" +dependencies = [ + "anyhow", + "bytes", + "futures", + "http", + "reqwest", + "thiserror", + "tokio", + "tracing", + "url", + "wasmtime", +] + [[package]] name = "wasi-tokio" version = "7.0.0" @@ -3239,6 +3703,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.80" @@ -3506,6 +3982,7 @@ dependencies = [ "tempfile", "test-programs", "tokio", + "wasi-experimental-http-wasmtime", "wasmparser", "wasmtime", "wasmtime-cache", @@ -4092,6 +4569,15 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "winx" version = "0.35.0" diff --git a/Cargo.toml b/Cargo.toml index 95e892a1ccd2..8b4425482a53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ wasmtime-wasi = { workspace = true, features = ["exit"] } wasmtime-wasi-crypto = { workspace = true, optional = true } wasmtime-wasi-nn = { workspace = true, optional = true } wasmtime-wasi-threads = { workspace = true, optional = true } +wasi-experimental-http-wasmtime = { workspace = true } clap = { workspace = true, features = ["color", "suggestions", "derive"] } anyhow = { workspace = true } target-lexicon = { workspace = true } @@ -156,6 +157,8 @@ cranelift-fuzzgen = { path = "cranelift/fuzzgen" } cranelift-bforest = { path = "cranelift/bforest", version = "0.94.0" } cranelift = { path = "cranelift/umbrella", version = "0.94.0" } +wasi-experimental-http-wasmtime = { path = "crates/wasi-experimental-http-wasmtime", version = "=0.10.0" } + winch-codegen = { path = "winch/codegen", version = "=0.5.0" } winch-filetests = { path = "winch/filetests" } winch-test-macros = { path = "winch/test-macros" } diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 7e40bff3aa8d..7151c307a94a 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -62,6 +62,10 @@ pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[ "experimental-wasi-threads", "enables support for the WASI threading API (experimental), see https://github.com/WebAssembly/wasi-threads", ), + ( + "experimental-wasi-http", + "enables support for the WASI HTTP APIs (experimental), see https://github.com/deislabs/wasi-experimental-http", + ), ]; fn pick_profiling_strategy(jitdump: bool, vtune: bool) -> Result { @@ -473,6 +477,7 @@ fn parse_wasi_modules(modules: &str) -> Result { "experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable), "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable), "experimental-wasi-threads" => Ok(wasi_modules.wasi_threads = enable), + "experimental-wasi-http" => Ok(wasi_modules.wasi_http = enable), "default" => bail!("'default' cannot be specified with other WASI modules"), _ => bail!("unsupported WASI module '{}'", module), }; @@ -507,6 +512,9 @@ pub struct WasiModules { /// Enable the experimental wasi-threads implementation. pub wasi_threads: bool, + + /// Enable the experimental wasi-http implementation + pub wasi_http: bool, } impl Default for WasiModules { @@ -516,6 +524,7 @@ impl Default for WasiModules { wasi_crypto: false, wasi_nn: false, wasi_threads: false, + wasi_http: false, } } } @@ -528,6 +537,7 @@ impl WasiModules { wasi_nn: false, wasi_crypto: false, wasi_threads: false, + wasi_http: false, } } } @@ -675,7 +685,8 @@ mod test { wasi_common: true, wasi_crypto: false, wasi_nn: false, - wasi_threads: false + wasi_threads: false, + wasi_http: false, } ); } @@ -689,7 +700,8 @@ mod test { wasi_common: true, wasi_crypto: false, wasi_nn: false, - wasi_threads: false + wasi_threads: false, + wasi_http: false } ); } @@ -707,7 +719,8 @@ mod test { wasi_common: false, wasi_crypto: false, wasi_nn: true, - wasi_threads: false + wasi_threads: false, + wasi_http: false, } ); } @@ -722,7 +735,8 @@ mod test { wasi_common: false, wasi_crypto: false, wasi_nn: false, - wasi_threads: false + wasi_threads: false, + wasi_http: false, } ); } diff --git a/crates/wasi-experimental-http-wasmtime/Cargo.toml b/crates/wasi-experimental-http-wasmtime/Cargo.toml new file mode 100644 index 000000000000..fd502eabfce4 --- /dev/null +++ b/crates/wasi-experimental-http-wasmtime/Cargo.toml @@ -0,0 +1,24 @@ +[package] + name = "wasi-experimental-http-wasmtime" + version = "0.10.0" + authors = [ "Radu Matei " ] + edition = "2021" + repository = "https://github.com/deislabs/wasi-experimental-http" + license = "MIT" + description = "Experimental HTTP library for WebAssembly in Wasmtime" + readme = "readme.md" + +[dependencies] + anyhow = "1.0" + bytes = "1" + futures = "0.3" + http = "0.2" + reqwest = { version = "0.11", default-features = true, features = [ + "json", + "blocking", + ] } + thiserror = "1.0" + tokio = { version = "1.4.0", features = [ "full" ] } + tracing = { version = "0.1", features = [ "log" ] } + url = "2.2.1" + wasmtime = { workspace = true } diff --git a/crates/wasi-experimental-http-wasmtime/readme.md b/crates/wasi-experimental-http-wasmtime/readme.md new file mode 100644 index 000000000000..b17b0e4489d0 --- /dev/null +++ b/crates/wasi-experimental-http-wasmtime/readme.md @@ -0,0 +1,34 @@ +# `wasi-experimental-http-wasmtime` + +![Crates.io](https://img.shields.io/crates/v/wasi-experimental-http-wasmtime) + +Experimental HTTP library for WebAssembly in Wasmtime + +### Adding support to a Wasmtime runtime + +The easiest way to add support is by using the +[Wasmtime linker](https://docs.rs/wasmtime/0.26.0/wasmtime/struct.Linker.html): + +```rust +let store = Store::default(); +let mut linker = Linker::new(&store); +let wasi = Wasi::new(&store, ctx); + +// link the WASI core functions +wasi.add_to_linker(&mut linker)?; + +// link the experimental HTTP support +let allowed_hosts = Some(vec!["https://postman-echo.com".to_string()]); +let max_concurrent_requests = Some(42); + +let http = HttpCtx::new(allowed_domains, max_concurrent_requests)?; +http.add_to_linker(&mut linker)?; +``` + +The Wasmtime implementation also enables allowed domains - an optional and +configurable list of domains or hosts that guest modules are allowed to send +requests to. If `None` or an empty vector is passed, guest modules are **NOT** +allowed to make HTTP requests to any server. (Note that the hosts passed MUST +have the protocol also specified - i.e. `https://my-domain.com`, or +`http://192.168.0.1`, and if making requests to a subdomain, the subdomain MUST +be in the allowed list. See the the library tests for more examples). diff --git a/crates/wasi-experimental-http-wasmtime/src/lib.rs b/crates/wasi-experimental-http-wasmtime/src/lib.rs new file mode 100644 index 000000000000..ab4583ab6786 --- /dev/null +++ b/crates/wasi-experimental-http-wasmtime/src/lib.rs @@ -0,0 +1,776 @@ +use anyhow::Error; +use bytes::Bytes; +use futures::executor::block_on; +use http::{header::HeaderName, HeaderMap, HeaderValue}; +use reqwest::{Client, Method}; +use std::{ + collections::HashMap, + str::FromStr, + sync::{Arc, PoisonError, RwLock}, +}; +use tokio::runtime::Handle; +use url::Url; +use wasmtime::*; + +const MEMORY: &str = "memory"; +const ALLOW_ALL_HOSTS: &str = "insecure:allow-all"; + +pub type WasiHttpHandle = u32; + +/// Response body for HTTP requests, consumed by guest modules. +struct Body { + bytes: Bytes, + pos: usize, +} + +/// An HTTP response abstraction that is persisted across multiple +/// host calls. +struct Response { + headers: HeaderMap, + body: Body, +} + +/// Host state for the responses of the instance. +#[derive(Default)] +struct State { + responses: HashMap, + current_handle: WasiHttpHandle, +} + +#[derive(Debug, thiserror::Error)] +enum HttpError { + #[error("Invalid handle: [{0}]")] + InvalidHandle(WasiHttpHandle), + #[error("Memory not found")] + MemoryNotFound, + #[error("Memory access error")] + MemoryAccessError(#[from] wasmtime::MemoryAccessError), + #[error("Buffer too small")] + BufferTooSmall, + #[error("Header not found")] + HeaderNotFound, + #[error("UTF-8 error")] + Utf8Error(#[from] std::str::Utf8Error), + #[error("Destination not allowed")] + DestinationNotAllowed(String), + #[error("Invalid method")] + InvalidMethod, + #[error("Invalid encoding")] + InvalidEncoding, + #[error("Invalid URL")] + InvalidUrl, + #[error("HTTP error")] + RequestError(#[from] reqwest::Error), + #[error("Runtime error")] + RuntimeError, + #[error("Too many sessions")] + TooManySessions, +} + +impl From for u32 { + fn from(e: HttpError) -> u32 { + match e { + HttpError::InvalidHandle(_) => 1, + HttpError::MemoryNotFound => 2, + HttpError::MemoryAccessError(_) => 3, + HttpError::BufferTooSmall => 4, + HttpError::HeaderNotFound => 5, + HttpError::Utf8Error(_) => 6, + HttpError::DestinationNotAllowed(_) => 7, + HttpError::InvalidMethod => 8, + HttpError::InvalidEncoding => 9, + HttpError::InvalidUrl => 10, + HttpError::RequestError(_) => 11, + HttpError::RuntimeError => 12, + HttpError::TooManySessions => 13, + } + } +} + +impl From>> for HttpError { + fn from(_: PoisonError>) -> Self { + HttpError::RuntimeError + } +} + +impl From>> for HttpError { + fn from(_: PoisonError>) -> Self { + HttpError::RuntimeError + } +} + +impl From> for HttpError { + fn from(_: PoisonError<&mut State>) -> Self { + HttpError::RuntimeError + } +} + +struct HostCalls; + +impl HostCalls { + /// Remove the current handle from the state. + /// Depending on the implementation, guest modules might + /// have to manually call `close`. + // TODO (@radu-matei) + // Fix the clippy warning. + #[allow(clippy::unnecessary_wraps)] + fn close(st: Arc>, handle: WasiHttpHandle) -> Result<(), HttpError> { + let mut st = st.write()?; + st.responses.remove(&handle); + Ok(()) + } + + /// Read `buf_len` bytes from the response of `handle` and + /// write them into `buf_ptr`. + fn body_read( + st: Arc>, + memory: Memory, + mut store: impl AsContextMut, + handle: WasiHttpHandle, + buf_ptr: u32, + buf_len: u32, + buf_read_ptr: u32, + ) -> Result<(), HttpError> { + let mut st = st.write()?; + + let mut body = &mut st.responses.get_mut(&handle).unwrap().body; + let mut context = store.as_context_mut(); + + // Write at most either the remaining of the response body, or the entire + // length requested by the guest. + let available = std::cmp::min(buf_len as _, body.bytes.len() - body.pos); + memory.write( + &mut context, + buf_ptr as _, + &body.bytes[body.pos..body.pos + available], + )?; + body.pos += available; + // Write the number of bytes written back to the guest. + memory.write( + &mut context, + buf_read_ptr as _, + &(available as u32).to_le_bytes(), + )?; + Ok(()) + } + + /// Get a response header value given a key. + #[allow(clippy::too_many_arguments)] + fn header_get( + st: Arc>, + memory: Memory, + mut store: impl AsContextMut, + handle: WasiHttpHandle, + name_ptr: u32, + name_len: u32, + value_ptr: u32, + value_len: u32, + value_written_ptr: u32, + ) -> Result<(), HttpError> { + let st = st.read()?; + + // Get the current response headers. + let headers = &st + .responses + .get(&handle) + .ok_or(HttpError::InvalidHandle(handle))? + .headers; + + let mut store = store.as_context_mut(); + + // Read the header key from the module's memory. + let key = string_from_memory(&memory, &mut store, name_ptr, name_len)?.to_ascii_lowercase(); + // Attempt to get the corresponding value from the resposne headers. + let value = headers.get(key).ok_or(HttpError::HeaderNotFound)?; + if value.len() > value_len as _ { + return Err(HttpError::BufferTooSmall); + } + // Write the header value and its length. + memory.write(&mut store, value_ptr as _, value.as_bytes())?; + memory.write( + &mut store, + value_written_ptr as _, + &(value.len() as u32).to_le_bytes(), + )?; + Ok(()) + } + + fn headers_get_all( + st: Arc>, + memory: Memory, + mut store: impl AsContextMut, + handle: WasiHttpHandle, + buf_ptr: u32, + buf_len: u32, + buf_written_ptr: u32, + ) -> Result<(), HttpError> { + let st = st.read()?; + + let headers = &st + .responses + .get(&handle) + .ok_or(HttpError::InvalidHandle(handle))? + .headers; + + let headers = match header_map_to_string(headers) { + Ok(res) => res, + Err(_) => return Err(HttpError::RuntimeError), + }; + + if headers.len() > buf_len as _ { + return Err(HttpError::BufferTooSmall); + } + + let mut store = store.as_context_mut(); + + memory.write(&mut store, buf_ptr as _, headers.as_bytes())?; + memory.write( + &mut store, + buf_written_ptr as _, + &(headers.len() as u32).to_le_bytes(), + )?; + Ok(()) + } + + /// Execute a request for a guest module, given + /// the request data. + #[allow(clippy::too_many_arguments)] + fn req( + st: Arc>, + allowed_hosts: Option<&[String]>, + max_concurrent_requests: Option, + memory: Memory, + mut store: impl AsContextMut, + url_ptr: u32, + url_len: u32, + method_ptr: u32, + method_len: u32, + req_headers_ptr: u32, + req_headers_len: u32, + req_body_ptr: u32, + req_body_len: u32, + status_code_ptr: u32, + res_handle_ptr: u32, + ) -> Result<(), HttpError> { + let span = tracing::trace_span!("req"); + let _enter = span.enter(); + + let mut st = st.write()?; + + if let Some(max) = max_concurrent_requests { + if st.responses.len() > (max - 1) as usize { + return Err(HttpError::TooManySessions); + } + }; + let mut store = store.as_context_mut(); + + // Read the request parts from the module's linear memory and check early if + // the guest is allowed to make a request to the given URL. + let url = string_from_memory(&memory, &mut store, url_ptr, url_len)?; + if !is_allowed(url.as_str(), allowed_hosts)? { + return Err(HttpError::DestinationNotAllowed(url)); + } + + let method = Method::from_str( + string_from_memory(&memory, &mut store, method_ptr, method_len)?.as_str(), + ) + .map_err(|_| HttpError::InvalidMethod)?; + let req_body = slice_from_memory(&memory, &mut store, req_body_ptr, req_body_len)?; + let headers = string_to_header_map( + string_from_memory(&memory, &mut store, req_headers_ptr, req_headers_len)?.as_str(), + ) + .map_err(|_| HttpError::InvalidEncoding)?; + + // Send the request. + let (status, resp_headers, resp_body) = + request(url.as_str(), headers, method, req_body.as_slice())?; + tracing::debug!( + status, + ?resp_headers, + body_len = resp_body.as_ref().len(), + "got HTTP response, writing back to memory" + ); + + // Write the status code to the guest. + memory.write(&mut store, status_code_ptr as _, &status.to_le_bytes())?; + + // Construct the response, add it to the current state, and write + // the handle to the guest. + let response = Response { + headers: resp_headers, + body: Body { + bytes: resp_body, + pos: 0, + }, + }; + + let initial_handle = st.current_handle; + while st.responses.get(&st.current_handle).is_some() { + st.current_handle += 1; + if st.current_handle == initial_handle { + return Err(HttpError::TooManySessions); + } + } + let handle = st.current_handle; + st.responses.insert(handle, response); + memory.write(&mut store, res_handle_ptr as _, &handle.to_le_bytes())?; + + Ok(()) + } +} + +pub fn add_to_linker( + linker: &mut wasmtime::Linker, + get_cx: impl Fn(&T) -> &HttpCtx + Send + Sync + 'static, +) -> anyhow::Result<()> { + let http = HttpState::new()?; + http.add_to_linker(linker, get_cx)?; + Ok(()) +} + +/// Per-instance context data used to control whether the guest +/// is allowed to make an outbound HTTP request. +#[derive(Clone)] +pub struct HttpCtx { + pub allowed_hosts: Option>, + pub max_concurrent_requests: Option, +} + +/// Experimental HTTP extension object for Wasmtime. +pub struct HttpState { + state: Arc>, +} + +impl HttpState { + /// Module the HTTP extension is going to be defined as. + pub const MODULE: &'static str = "wasi_experimental_http"; + + /// Create a new HTTP extension object. + /// `allowed_hosts` may be `None` (no outbound connections allowed) + /// or a list of allowed host names. + pub fn new() -> Result { + let state = Arc::new(RwLock::new(State::default())); + Ok(HttpState { state }) + } + + pub fn add_to_linker( + &self, + linker: &mut Linker, + get_cx: impl Fn(&T) -> &HttpCtx + Send + Sync + 'static, + ) -> Result<(), Error> { + let st = self.state.clone(); + linker.func_wrap( + Self::MODULE, + "close", + move |handle: WasiHttpHandle| -> u32 { + match HostCalls::close(st.clone(), handle) { + Ok(()) => 0, + Err(e) => e.into(), + } + }, + )?; + + let st = self.state.clone(); + linker.func_wrap( + Self::MODULE, + "body_read", + move |mut caller: Caller<'_, T>, + handle: WasiHttpHandle, + buf_ptr: u32, + buf_len: u32, + buf_read_ptr: u32| + -> u32 { + let memory = match memory_get(&mut caller) { + Ok(m) => m, + Err(e) => return e.into(), + }; + + let ctx = caller.as_context_mut(); + + match HostCalls::body_read( + st.clone(), + memory, + ctx, + handle, + buf_ptr, + buf_len, + buf_read_ptr, + ) { + Ok(()) => 0, + Err(e) => e.into(), + } + }, + )?; + + let st = self.state.clone(); + linker.func_wrap( + Self::MODULE, + "header_get", + move |mut caller: Caller<'_, T>, + handle: WasiHttpHandle, + name_ptr: u32, + name_len: u32, + value_ptr: u32, + value_len: u32, + value_written_ptr: u32| + -> u32 { + let memory = match memory_get(&mut caller) { + Ok(m) => m, + Err(e) => return e.into(), + }; + + let ctx = caller.as_context_mut(); + + match HostCalls::header_get( + st.clone(), + memory, + ctx, + handle, + name_ptr, + name_len, + value_ptr, + value_len, + value_written_ptr, + ) { + Ok(()) => 0, + Err(e) => e.into(), + } + }, + )?; + + let st = self.state.clone(); + linker.func_wrap( + Self::MODULE, + "headers_get_all", + move |mut caller: Caller<'_, T>, + handle: WasiHttpHandle, + buf_ptr: u32, + buf_len: u32, + buf_read_ptr: u32| + -> u32 { + let memory = match memory_get(&mut caller) { + Ok(m) => m, + Err(e) => return e.into(), + }; + + let ctx = caller.as_context_mut(); + + match HostCalls::headers_get_all( + st.clone(), + memory, + ctx, + handle, + buf_ptr, + buf_len, + buf_read_ptr, + ) { + Ok(()) => 0, + Err(e) => e.into(), + } + }, + )?; + + let st = self.state.clone(); + linker.func_wrap( + Self::MODULE, + "req", + move |mut caller: Caller<'_, T>, + url_ptr: u32, + url_len: u32, + method_ptr: u32, + method_len: u32, + req_headers_ptr: u32, + req_headers_len: u32, + req_body_ptr: u32, + req_body_len: u32, + status_code_ptr: u32, + res_handle_ptr: u32| + -> u32 { + let memory = match memory_get(&mut caller) { + Ok(m) => m, + Err(e) => return e.into(), + }; + + let ctx = caller.as_context_mut(); + let http_ctx = get_cx(ctx.data()); + let max_concurrent_requests = http_ctx.max_concurrent_requests; + // TODO: There is probably a way to avoid this copy. + let allowed_hosts = http_ctx.allowed_hosts.clone(); + + match HostCalls::req( + st.clone(), + allowed_hosts.as_deref(), + max_concurrent_requests, + memory, + ctx, + url_ptr, + url_len, + method_ptr, + method_len, + req_headers_ptr, + req_headers_len, + req_body_ptr, + req_body_len, + status_code_ptr, + res_handle_ptr, + ) { + Ok(()) => 0, + Err(e) => e.into(), + } + }, + )?; + + Ok(()) + } +} + +#[tracing::instrument] +fn request( + url: &str, + headers: HeaderMap, + method: Method, + body: &[u8], +) -> Result<(u16, HeaderMap, Bytes), HttpError> { + tracing::debug!( + %url, + ?headers, + ?method, + body_len = body.len(), + "performing request" + ); + let url: Url = url.parse().map_err(|_| HttpError::InvalidUrl)?; + let body = body.to_vec(); + match Handle::try_current() { + Ok(r) => { + // If running in a Tokio runtime, spawn a new blocking executor + // that will send the HTTP request, and block on its execution. + // This attempts to avoid any deadlocks from other operations + // already executing on the same executor (compared with just + // blocking on the current one). + // + // This should only be a temporary workaround, until we take + // advantage of async functions in Wasmtime. + tracing::trace!("tokio runtime available, spawning request on tokio thread"); + block_on(r.spawn_blocking(move || { + let client = Client::builder().build().unwrap(); + let res = block_on( + client + .request(method, url) + .headers(headers) + .body(body) + .send(), + )?; + Ok(( + res.status().as_u16(), + res.headers().clone(), + block_on(res.bytes())?, + )) + })) + .map_err(|_| HttpError::RuntimeError)? + } + Err(_) => { + tracing::trace!("no tokio runtime available, using blocking request"); + let res = reqwest::blocking::Client::new() + .request(method, url) + .headers(headers) + .body(body) + .send()?; + return Ok((res.status().as_u16(), res.headers().clone(), res.bytes()?)); + } + } +} + +/// Get the exported memory block called `memory`. +/// This will return an `HttpError::MemoryNotFound` if the module does +/// not export a memory block. +fn memory_get(caller: &mut Caller<'_, T>) -> Result { + if let Some(Extern::Memory(mem)) = caller.get_export(MEMORY) { + Ok(mem) + } else { + Err(HttpError::MemoryNotFound) + } +} + +/// Get a slice of length `len` from `memory`, starting at `offset`. +/// This will return an `HttpError::BufferTooSmall` if the size of the +/// requested slice is larger than the memory size. +fn slice_from_memory( + memory: &Memory, + mut ctx: impl AsContextMut, + offset: u32, + len: u32, +) -> Result, HttpError> { + let required_memory_size = offset.checked_add(len).ok_or(HttpError::BufferTooSmall)? as usize; + + if required_memory_size > memory.data_size(&mut ctx) { + return Err(HttpError::BufferTooSmall); + } + + let mut buf = vec![0u8; len as usize]; + memory.read(&mut ctx, offset as usize, buf.as_mut_slice())?; + Ok(buf) +} + +/// Read a string of byte length `len` from `memory`, starting at `offset`. +fn string_from_memory( + memory: &Memory, + ctx: impl AsContextMut, + offset: u32, + len: u32, +) -> Result { + let slice = slice_from_memory(memory, ctx, offset, len)?; + Ok(std::str::from_utf8(&slice)?.to_string()) +} + +/// Check if guest module is allowed to send request to URL, based on the list of +/// allowed hosts defined by the runtime. +/// If `None` is passed, the guest module is not allowed to send the request. +fn is_allowed(url: &str, allowed_hosts: Option<&[String]>) -> Result { + let url_host = Url::parse(url) + .map_err(|_| HttpError::InvalidUrl)? + .host_str() + .ok_or(HttpError::InvalidUrl)? + .to_owned(); + match allowed_hosts { + Some(domains) => { + // check domains has any "insecure:allow-all" wildcard + if domains.iter().any(|domain| domain == ALLOW_ALL_HOSTS) { + Ok(true) + } else { + let allowed: Result, _> = domains.iter().map(|d| Url::parse(d)).collect(); + let allowed = allowed.map_err(|_| HttpError::InvalidUrl)?; + + Ok(allowed + .iter() + .map(|u| u.host_str().unwrap()) + .any(|x| x == url_host.as_str())) + } + } + None => Ok(false), + } +} + +// The following two functions are copied from the `wasi_experimental_http` +// crate, because the Windows linker apparently cannot handle unresolved +// symbols from a crate, even when the caller does not actually use any of the +// external symbols. +// +// https://github.com/rust-lang/rust/issues/86125 + +/// Decode a header map from a string. +fn string_to_header_map(s: &str) -> Result { + let mut headers = HeaderMap::new(); + for entry in s.lines() { + let mut parts = entry.splitn(2, ':'); + #[allow(clippy::or_fun_call)] + let k = parts.next().ok_or(anyhow::format_err!( + "Invalid serialized header: [{}]", + entry + ))?; + let v = parts.next().unwrap(); + headers.insert(HeaderName::from_str(k)?, HeaderValue::from_str(v)?); + } + Ok(headers) +} + +/// Encode a header map as a string. +fn header_map_to_string(hm: &HeaderMap) -> Result { + let mut res = String::new(); + for (name, value) in hm + .iter() + .map(|(name, value)| (name.as_str(), std::str::from_utf8(value.as_bytes()))) + { + let value = value?; + anyhow::ensure!( + !name + .chars() + .any(|x| x.is_control() || "(),/:;<=>?@[\\]{}".contains(x)), + "Invalid header name" + ); + anyhow::ensure!( + !value.chars().any(|x| x.is_control()), + "Invalid header value" + ); + res.push_str(&format!("{}:{}\n", name, value)); + } + Ok(res) +} + +#[test] +#[allow(clippy::bool_assert_comparison)] +fn test_allowed_domains() { + let allowed_domains = vec![ + "https://api.brigade.sh".to_string(), + "https://example.com".to_string(), + "http://192.168.0.1".to_string(), + ]; + + assert_eq!( + true, + is_allowed( + "https://api.brigade.sh/healthz", + Some(allowed_domains.as_ref()) + ) + .unwrap() + ); + assert_eq!( + true, + is_allowed( + "https://example.com/some/path/with/more/paths", + Some(allowed_domains.as_ref()) + ) + .unwrap() + ); + assert_eq!( + true, + is_allowed("http://192.168.0.1/login", Some(allowed_domains.as_ref())).unwrap() + ); + assert_eq!( + false, + is_allowed("https://test.brigade.sh", Some(allowed_domains.as_ref())).unwrap() + ); +} + +#[test] +#[allow(clippy::bool_assert_comparison)] +fn test_allowed_domains_with_wildcard() { + let allowed_domains = vec![ + "https://example.com".to_string(), + ALLOW_ALL_HOSTS.to_string(), + "http://192.168.0.1".to_string(), + ]; + + assert_eq!( + true, + is_allowed( + "https://api.brigade.sh/healthz", + Some(allowed_domains.as_ref()) + ) + .unwrap() + ); + assert_eq!( + true, + is_allowed( + "https://example.com/some/path/with/more/paths", + Some(allowed_domains.as_ref()) + ) + .unwrap() + ); + assert_eq!( + true, + is_allowed("http://192.168.0.1/login", Some(allowed_domains.as_ref())).unwrap() + ); + assert_eq!( + true, + is_allowed("https://test.brigade.sh", Some(allowed_domains.as_ref())).unwrap() + ); +} + +#[test] +#[should_panic] +#[allow(clippy::bool_assert_comparison)] +fn test_url_parsing() { + let allowed_domains = vec![ALLOW_ALL_HOSTS.to_string()]; + + is_allowed("not even a url", Some(allowed_domains.as_ref())).unwrap(); +} diff --git a/src/commands/run.rs b/src/commands/run.rs index 4f5751c3befe..0951d80022af 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -24,6 +24,8 @@ use wasmtime_wasi_crypto::WasiCryptoCtx; #[cfg(feature = "wasi-threads")] use wasmtime_wasi_threads::WasiThreadsCtx; +use wasi_experimental_http_wasmtime::HttpCtx; + fn parse_module(s: &OsStr) -> anyhow::Result { // Do not accept wasmtime subcommand names as the module name match s.to_str() { @@ -424,6 +426,7 @@ struct Host { wasi_nn: Option>, #[cfg(feature = "wasi-threads")] wasi_threads: Option>>, + wasi_experimental_http_wasmtime: Option, } /// Populates the given `Linker` with WASI APIs. @@ -523,6 +526,44 @@ fn populate_with_wasi( } } + if wasi_modules.wasi_http { + let mut allowed_list = vec![]; + let host_config = std::env::var("WASI_HTTP_ALLOWED_HOSTS"); + if !host_config.is_err() { + for piece in host_config.unwrap().split(",") { + allowed_list.push(piece.to_string()); + } + } + let allowed_hosts = Some(allowed_list); + + let max_requests; + let max_request_config = std::env::var("WASI_HTTP_MAX_REQUESTS"); + match max_request_config { + Ok(value) => match value.parse::() { + Ok(parsed) => { + max_requests = parsed; + } + Err(_) => { + max_requests = 1; + } + }, + Err(_) => { + max_requests = 1; + } + } + let max_concurrent_requests = Some(max_requests); + + let http = HttpCtx { + allowed_hosts, + max_concurrent_requests, + }; + + wasi_experimental_http_wasmtime::add_to_linker(linker, |host| { + host.wasi_experimental_http_wasmtime.as_ref().unwrap() + })?; + store.data_mut().wasi_experimental_http_wasmtime = Some(http); + } + Ok(()) }