From 3727713df362561b753ee785b7c61d07c60f8330 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 19 Feb 2022 02:17:32 +0000 Subject: [PATCH] Initial commit --- .gitignore | 2 ++ Cargo.lock | 1787 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 30 ++++++++++++++++++++++++++++++ LICENSE | 14 ++++++++++++++ README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/protocol/codec.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/protocol/high_level.rs | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/protocol/low_level.rs | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/protocol/mod.rs | 4 ++++ src/protocol/packet_line.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/providers/gitlab.rs | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/providers/mod.rs | 37 +++++++++++++++++++++++++++++++++++++ src/util.rs | 18 ++++++++++++++++++ 14 files changed, 3118 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/main.rs create mode 100644 src/protocol/codec.rs create mode 100644 src/protocol/high_level.rs create mode 100644 src/protocol/low_level.rs create mode 100644 src/protocol/mod.rs create mode 100644 src/protocol/packet_line.rs create mode 100644 src/providers/gitlab.rs create mode 100644 src/providers/mod.rs create mode 100644 src/util.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d81f12e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/.idea diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..20df39b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1787 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "ctr", + "opaque-debug", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-trait" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64ct" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0" + +[[package]] +name = "bcrypt-pbkdf" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c38c03b9506bd92bf1ef50665a81eda156f615438f7654bffba58907e6149d7" +dependencies = [ + "blowfish", + "crypto-mac", + "pbkdf2", + "sha2", + "zeroize", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-modes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" +dependencies = [ + "block-padding", + "cipher", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "blowfish" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe3ff3fc1de48c1ac2e3341c4df38b0d1bfb8fdf04632a187c8b75aaa319a7ab" +dependencies = [ + "byteorder", + "cipher", + "opaque-debug", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[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 = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "cryptovec" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc7fa13a6bbb2322d325292c57f4c8e7291595506f8289968a0eb61c3130bdf" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "encoding_rs" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +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.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "git-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "arrayvec", + "async-trait", + "base64", + "bytes", + "flate2", + "futures", + "hex", + "indexmap", + "indoc", + "itoa", + "parse_link_header", + "percent-encoding", + "reqwest", + "serde", + "sha1", + "shlex", + "thrussh", + "thrussh-keys", + "time", + "tokio", + "tokio-util 0.7.0", +] + +[[package]] +name = "h2" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.6.9", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-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 = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7906a9fababaeacb774f72410e497a1d18de916322e33797bb2cd29baa23c9e" +dependencies = [ + "unindent", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" + +[[package]] +name = "libsodium-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b779387cd56adfbc02ea4a668e704f729be8d6a6abd2c27ca5ee537849a92fd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "walkdir", +] + +[[package]] +name = "lock_api" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[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.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[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.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "parse_link_header" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40728c9c01de984c45f49385ab054fdc31cd3322658a6934347887e72cb48df9" +dependencies = [ + "http", + "lazy_static", + "regex", +] + +[[package]] +name = "password-hash" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e0b28ace46c5a396546bcf443bf422b57049617433d8854227352a4a9b24e7" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "base64ct", + "crypto-mac", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +dependencies = [ + "lazy_static", + "winapi", +] + +[[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.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thrussh" +version = "0.33.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6540238a9adf83df6e66541c182a52acf892ab335595ca965c229ade8536f8" +dependencies = [ + "bitflags", + "byteorder", + "cryptovec", + "digest 0.9.0", + "flate2", + "futures", + "generic-array", + "log", + "rand", + "sha2", + "thiserror", + "thrussh-keys", + "thrussh-libsodium", + "tokio", +] + +[[package]] +name = "thrussh-keys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a72cc51a2932b18d92f7289332d8564cec4a5014063722a9d3fdca52c5d8f5ab" +dependencies = [ + "aes", + "bcrypt-pbkdf", + "bit-vec", + "block-modes", + "byteorder", + "cryptovec", + "data-encoding", + "dirs", + "futures", + "hmac", + "log", + "md5", + "num-bigint", + "num-integer", + "pbkdf2", + "rand", + "serde", + "serde_derive", + "sha2", + "thiserror", + "thrussh-libsodium", + "tokio", + "tokio-stream", + "yasna", +] + +[[package]] +name = "thrussh-libsodium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe89c70d27b1cb92e13bc8af63493e890d0de46dae4df0e28233f62b4ed9500" +dependencies = [ + "lazy_static", + "libc", + "libsodium-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "time" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +dependencies = [ + "libc", + "num_threads", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "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-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unindent" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "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.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + +[[package]] +name = "yasna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +dependencies = [ + "bit-vec", + "num-bigint", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c06303a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "git-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1" +async-trait = "0.1" +arrayvec = "0.7" +base64 = "0.13" +bytes = "1.1" +flate2 = "1.0" +futures = "0.3" +hex = "0.4" +itoa = "1.0" +indexmap = "1.8" +indoc = "1.0" +parse_link_header = "0.3" +percent-encoding = "2.1" +reqwest = { version = "0.11", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +sha1 = "0.10" +shlex = "1.1" +thrussh = "0.33" +thrussh-keys = "0.21" +time = "0.3" +tokio = { version = "1.17", features = ["full"] } +tokio-util = { version = "0.7", features = ["codec"] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8c3bdb1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6699d2 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# gitlab-cargo-shim + +Say goodbye to your Git dependencies, `gitlab-cargo-shim` is an SSH server +that serves crates just like a standard Cargo registry but from a +[GitLab package registry][gitlab-package-registry], allowing you to use +your private dependencies like any other dependency. No more `git push --force`s +breaking your builds & proper versioning in one simple little binary. + +Access controls work just like they do in GitLab, builds are scoped to +users - if they don't have permission to the dependency they can't build +it, it's that simple. + +Users are identified by their SSH keys when connecting to the server and +will be authenticated to the GitLab API via an [impersonation token][imp-token], +builds will insert their token as a username to the SSH server and the +shim will use that to call the GitLab API. + +To publish simply run `cargo package` and push the resulting `.crate` file +to the GitLab package repository with a semver-compatible version string, to +consume the package simply configure your `.cargo/config.toml` and `Cargo.toml` +accordingly. + +```toml +# .cargo/config.toml +[registries] +my-gitlab-group = { index = "ssh://gitlab-cargo-shim.local/my-gitlab-group" } + +# Cargo.toml +[dependencies] +my-crate = { version = "0.1", registry = "my-gitlab-group" } +``` + +In your CI build, setup a `before_script` step to replace the connection string +with one containing the CI token: + +```yaml +# .gitlab-ci.yml +before_script: + - sed -i "s/(gitlab-cargo-shim.local)/gitlab-ci-token:$GITLAB-CI-TOKEN@\1/" .cargo/config.toml +``` + +(or add the corresponding [environment variable][envvar]) + +It's that easy. Go forth and enjoy your newfound quality of life improvements, +Rustacean. + +[gitlab-package-registry]: https://docs.gitlab.com/ee/user/packages/package_registry/index.html +[imp-token]: https://docs.gitlab.com/ee/api/index.html#impersonation-tokens +[envvar]: https://doc.rust-lang.org/cargo/reference/registries.html#using-an-alternate-registry diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4b03ddb --- /dev/null +++ b/src/main.rs @@ -0,0 +1,267 @@ +pub mod protocol; +pub mod providers; +pub mod util; + +use crate::{providers::{gitlab::Gitlab, PackageProvider, Release, User, UserProvider}, protocol::{codec::Encoder, packet_line::PktLine}}; +use futures::Future; +use std::{net::SocketAddr, pin::Pin, sync::Arc, fmt::Write}; +use bytes::BytesMut; +use thrussh::{server::{Auth, Session}, ChannelId, CryptoVec}; +use thrussh_keys::key::PublicKey; +use tokio::task::JoinHandle; +use tokio_util::codec::Encoder as CodecEncoder; +use crate::protocol::high_level::GitRepository; + +const AGENT: &str = concat!( + "agent=", + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION"), + "\n" +); + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let ed25519_key = thrussh_keys::key::KeyPair::generate_ed25519().unwrap(); + + let thrussh_config = Arc::new(thrussh::server::Config { + methods: thrussh::MethodSet::PUBLICKEY, + keys: vec![ed25519_key], + ..thrussh::server::Config::default() + }); + + let gitlab = Arc::new(Gitlab::new()?); + + thrussh::server::run(thrussh_config, "127.0.0.1:2222", Server { gitlab }).await?; + Ok(()) +} + +struct Server { + gitlab: Arc, +} + +impl thrussh::server::Server + for Server +{ + type Handler = Handler; + + fn new(&mut self, _peer_addr: Option) -> Self::Handler { + Handler { + gitlab: self.gitlab.clone(), + user: None, + group: None, + fetcher_future: None, + input_bytes: BytesMut::new(), + output_bytes: BytesMut::new(), + is_git_protocol_v2: false + } + } +} + +struct Handler { + gitlab: Arc, + user: Option, + group: Option, + fetcher_future: Option>>>, + input_bytes: BytesMut, + output_bytes: BytesMut, + is_git_protocol_v2: bool, +} + +impl Handler { + fn user(&self) -> anyhow::Result<&User> { + self.user.as_ref().ok_or(anyhow::anyhow!("no user set")) + } + + fn group(&self) -> anyhow::Result<&str> { + self.group.as_deref().ok_or(anyhow::anyhow!("no group set")) + } + + fn write(&mut self, packet: PktLine<'_>) -> Result<(), anyhow::Error> { + Encoder.encode(packet, &mut self.output_bytes) + } + + fn flush(&mut self, session: &mut Session, channel: ChannelId) { + session.data( + channel, + CryptoVec::from_slice(self.output_bytes.split().as_ref()), + ); + } + + async fn fetch_releases(&self, group: &str) -> anyhow::Result> { + let user = self.user()?; + self.gitlab.clone().fetch_releases_for_group(group, user.clone()).await + } +} + +type AsyncHandlerFut = + dyn Future as thrussh::server::Handler>::Error>> + Send; + +#[allow(clippy::type_complexity)] +impl<'a, U: UserProvider + PackageProvider + Send + Sync + 'static> thrussh::server::Handler + for Handler +{ + type Error = anyhow::Error; + type FutureAuth = Pin, Auth), U>>>; + type FutureUnit = Pin, Session), U>>>; + type FutureBool = futures::future::Ready>; + + fn finished_auth(self, auth: Auth) -> Self::FutureAuth { + Box::pin(futures::future::ready(Ok((self, auth)))) + } + + fn finished_bool(self, b: bool, session: Session) -> Self::FutureBool { + futures::future::ready(Ok((self, session, b))) + } + + fn finished(self, session: Session) -> Self::FutureUnit { + Box::pin(futures::future::ready(Ok((self, session)))) + } + + fn auth_publickey(mut self, user: &str, public_key: &PublicKey) -> Self::FutureAuth { + let fingerprint = public_key.fingerprint(); + let user = user.to_string(); + + Box::pin(async move { + let mut user = self + .gitlab + .find_user_by_username_password_combo(&user) + .await?; + + if user.is_none() { + user = self + .gitlab + .find_user_by_ssh_key(&util::format_fingerprint(&fingerprint)?) + .await?; + } + + self.user = Some(user.ok_or(anyhow::anyhow!("failed to find user"))?); + + self.finished_auth(Auth::Accept).await + }) + } + + fn data(mut self, channel: ChannelId, data: &[u8], mut session: Session) -> Self::FutureUnit { + self.input_bytes.extend_from_slice(data); + + Box::pin( + async move { + while let Some(frame) = self.codec.decode(&mut self.input_bytes)? { + // if the client flushed without giving us a command, we're expected to close + // the connection or else the client will just hang + if frame.command.is_empty() { + session.exit_status_request(channel, 0); + session.eof(channel); + session.close(channel); + return Ok((self, session)); + } + + let user = self.user()?; + let group = self.group()?; + + // start building the packfile we're going to send to the user + let mut packfile = GitRepository::default(); + } + + Ok((self, session)) + } + ) + } + + fn env_request( + mut self, + _channel: ChannelId, + name: &str, + value: &str, + session: Session, + ) -> Self::FutureUnit { + #[allow(clippy::single_match)] + match (name, value) { + ("GIT_PROTOCOL", "version=2") => self.is_git_protocol_v2 = true, + _ => {} + } + + Box::pin(futures::future::ready(Ok((self, session)))) + } + + fn shell_request(mut self, channel: ChannelId, mut session: Session) -> Self::FutureUnit { + Box::pin(async move { + let username = self.user()?.username.clone(); + write!( + &mut self.output_bytes, + "Hi there, {}! You've successfully authenticated, but {} does not provide shell access.\r\n", + username, + env!("CARGO_PKG_NAME") + )?; + self.flush(&mut session, channel); + session.close(channel); + Ok((self, session)) + }) + } + + /// Initially when setting up the SSH connection, the remote Git client will send us an + /// exec request (instead of the usual shell request that is sent when invoking `ssh`). + /// + /// The client will set `git-upload-pack` as the requested executable to run and also + /// sends the path that was appended to the end of the connection string defined in + /// cargo. + fn exec_request( + mut self, + channel: ChannelId, + data: &[u8], + mut session: Session, + ) -> Self::FutureUnit { + let data = match std::str::from_utf8(data) { + Ok(data) => data, + Err(e) => return Box::pin(futures::future::err(e.into())), + }; + // parses the given args in the same fashion as a POSIX shell + let args = shlex::split(data); + + Box::pin(async move { + // if the client didn't send `GIT_PROTOCOL=version=2` as an environment + // variable when connecting, we'll just close the connection + if !self.is_git_protocol_v2 { + anyhow::bail!("not git protocol v2"); + } + + let mut args = args.into_iter().flat_map(Vec::into_iter); + + // check the executable requested to be ran is the `git-upload-pack` we + // expect. we're not actually going to execute this, but we'll pretend + // to be it instead in `data`. + if args.next().as_deref() != Some("git-upload-pack") { + anyhow::bail!("not git-upload-pack"); + } + + // parse the requested group from the given path (the argument + // given to `git-upload-pack`) + if let Some(group) = args.next().filter(|v| v.as_str() != "/") { + let group = group + .trim_start_matches('/') + .trim_end_matches('/') + .to_string(); + self.group = Some(group); + } else { + session.extended_data(channel, 1, CryptoVec::from_slice(indoc::indoc! {b" + \r\nNo group was given in the path part of the SSH URI. A GitLab group should be defined in your .cargo/config.toml as follows: + [registries] + chartered = {{ index = \"ssh://domain.to.registry.com/my-group\" }}\r\n + "})); + session.close(channel); + } + + // preamble, sending our capabilities and what have you + self.write(PktLine::Data(b"version 2\n"))?; + self.write(PktLine::Data(AGENT.as_bytes()))?; + self.write(PktLine::Data(b"ls-refs=unborn\n"))?; + self.write(PktLine::Data(b"fetch=shallow wait-for-done\n"))?; + self.write(PktLine::Data(b"server-option\n"))?; + self.write(PktLine::Data(b"object-info\n"))?; + self.write(PktLine::Flush)?; + self.flush(&mut session, channel); + + Ok((self, session)) + }) + } +} diff --git a/src/protocol/codec.rs b/src/protocol/codec.rs new file mode 100644 index 0000000..c9671b3 --- /dev/null +++ b/src/protocol/codec.rs @@ -0,0 +1,136 @@ +#![allow(clippy::module_name_repetitions)] + +use bytes::{Buf, Bytes, BytesMut}; +use tokio_util::codec; + +use super::packet_line::PktLine; + +pub struct Encoder; + +impl codec::Encoder> for Encoder { + type Error = anyhow::Error; + + fn encode(&mut self, item: PktLine<'_>, dst: &mut BytesMut) -> Result<(), Self::Error> { + item.encode_to(dst)?; + Ok(()) + } +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct GitCommand { + pub command: Bytes, + pub metadata: Vec, +} + +#[derive(Default)] +pub struct GitCodec { + command: GitCommand, +} + +impl codec::Decoder for GitCodec { + type Item = GitCommand; + type Error = anyhow::Error; + + fn decode(&mut self, src: &mut bytes::BytesMut) -> Result, Self::Error> { + loop { + if src.len() < 4 { + return Ok(None); + } + + let mut length_bytes = [0_u8; 4]; + length_bytes.copy_from_slice(&src[..4]); + let length = u16::from_str_radix(std::str::from_utf8(&length_bytes)?, 16)? as usize; + + if length == 0 { + // flush + src.advance(4); + return Ok(Some(std::mem::take(&mut self.command))); + } else if length == 1 || length == 2 { + src.advance(4); + continue; + } else if !(4..=65520).contains(&length) { + return Err( + std::io::Error::new(std::io::ErrorKind::InvalidData, "protocol abuse").into(), + ); + } + + // not enough bytes in the buffer yet, ask for more + if src.len() < length { + src.reserve(length - src.len()); + return Ok(None); + } + + // length is inclusive of the 4 bytes that makes up itself + let mut data = src.split_to(length).freeze(); + data.advance(4); + + // strip newlines for conformity + if data.ends_with(b"\n") { + data.truncate(data.len() - 1); + } + + if self.command.command.is_empty() { + self.command.command = data; + } else { + self.command.metadata.push(data); + } + } + } +} + +#[cfg(test)] +mod test { + use bytes::{Bytes, BytesMut}; + use std::fmt::Write; + use tokio_util::codec::Decoder; + + #[test] + fn decode() { + let mut codec = super::GitCodec::default(); + + let mut bytes = BytesMut::new(); + + bytes.write_str("0015agent=git/2.32.0").unwrap(); + let res = codec.decode(&mut bytes).unwrap(); + assert_eq!(res, None); + + bytes.write_char('\n').unwrap(); + let res = codec.decode(&mut bytes).unwrap(); + assert_eq!(res, None); + + bytes.write_str("0000").unwrap(); + let res = codec.decode(&mut bytes).unwrap(); + assert_eq!( + res, + Some(super::GitCommand { + command: Bytes::from_static(b"agent=git/2.32.0"), + metadata: vec![], + }) + ); + + bytes.write_str("0000").unwrap(); + let res = codec.decode(&mut bytes).unwrap(); + assert_eq!( + res, + Some(super::GitCommand { + command: Bytes::new(), + metadata: vec![], + }) + ); + + bytes.write_str("0002").unwrap(); + bytes.write_str("0005a").unwrap(); + bytes.write_str("0001").unwrap(); + bytes.write_str("0005b").unwrap(); + bytes.write_str("0000").unwrap(); + + let res = codec.decode(&mut bytes).unwrap(); + assert_eq!( + res, + Some(super::GitCommand { + command: Bytes::from_static(b"a"), + metadata: vec![Bytes::from_static(b"b")], + }) + ); + } +} diff --git a/src/protocol/high_level.rs b/src/protocol/high_level.rs new file mode 100644 index 0000000..b560036 --- /dev/null +++ b/src/protocol/high_level.rs @@ -0,0 +1,165 @@ +//! A high-level interface for building packfiles. Wraps the `low_level` module +//! making a much easier interface for writing files and generating the root +//! commit. +//! +//! The output packfile will only have a single commit in it, which is fine +//! for our purposes because `cargo` will `git pull --force` from our Git +//! server, allowing us to ignore any history the client may have. + +use arrayvec::ArrayVec; +use indexmap::IndexMap; + +use super::low_level::{ + Commit, CommitUserInfo, HashOutput, PackFileEntry, TreeItem as LowLevelTreeItem, TreeItemKind, +}; + +/// The main way of interacting with the high level Packfile builder +/// +/// Builds a whole packfile containing files, directories and commits - essentially +/// building out a full Git repository in memory. +#[derive(Default, Debug)] +pub struct GitRepository<'a> { + /// A map containing all the blobs and their corresponding hashes so they're + /// not inserted more than once for any files in the whole tree with the same + /// content. + packfile_entries: IndexMap>, + /// An in-progress `Tree` currently being built out, the tree refers to items + /// in `file_entries` by hash. + tree: Tree<'a>, +} + +impl<'a> GitRepository<'a> { + /// Inserts a file into the repository, writing a file to the path + /// `path/to/my-file` would require a `path` of `["path", "to"]` + /// and a `file` of `"my-file"`. + pub fn insert( + &mut self, + path: ArrayVec<&'a str, N>, + file: &'a str, + content: &'a [u8], + ) -> Result<(), anyhow::Error> { + // we'll initialise the directory to the root of the tree, this means + // if a path isn't specified we'll just write it to the root directory + let mut directory = &mut self.tree; + + // loops through the parts in the path, recursing through the `directory` + // `Tree` until we get to our target directory, creating any missing + // directories along the way. + for part in path { + let tree_item = directory + .0 + .entry(part) + .or_insert_with(|| Box::new(TreeItem::Tree(Tree::default()))); + + if let TreeItem::Tree(d) = tree_item.as_mut() { + directory = d; + } else { + // TODO: how should we handle this? one of items we tried to + // recurse into was a directory. + anyhow::bail!("attempted to use a file as a directory"); + } + } + + // wrap the file in a Blob so it's ready for writing into the packfile, and also + // allows us to grab the hash of the file for use in the tree + let entry = PackFileEntry::Blob(content); + let file_hash = entry.hash()?; + + // todo: what should we do on overwrite? + directory + .0 + .insert(file, Box::new(TreeItem::Blob(file_hash))); + + self.packfile_entries.insert(file_hash, entry); + + Ok(()) + } + + /// Finalises this `GitRepository` by writing a commit to the packfile_entries, + /// all the files currently in the `tree`, returning all the packfile entries + /// and also the commit hash so it can be referred to by `ls-ref`s. + pub fn commit( + &'a mut self, + name: &'static str, + email: &'static str, + message: &'static str, + ) -> Result<(HashOutput, Vec>), anyhow::Error> { + // gets the hash of the entire tree from the root + let tree_hash = self.tree.to_packfile_entries(&mut self.packfile_entries)?; + + // build the commit using the given inputs + let commit_user = CommitUserInfo { + name, + email, + time: time::OffsetDateTime::now_utc(), + }; + + let commit = PackFileEntry::Commit(Commit { + tree: tree_hash, + author: commit_user, + committer: commit_user, + message, + }); + + // write the commit out to the packfile_entries + let commit_hash = commit.hash()?; + self.packfile_entries.insert(commit_hash, commit); + + // TODO: make PackFileEntry copy and remove this clone + Ok(( + commit_hash, + self.packfile_entries.values().cloned().collect(), + )) + } +} + +/// An in-progress tree builder, containing file hashes along with their names or nested trees +#[derive(Default, Debug)] +struct Tree<'a>(IndexMap<&'a str, Box>>); + +impl<'a> Tree<'a> { + /// Recursively writes the the whole tree out to the given `pack_file`, + /// the tree contains pointers to (hashes of) files contained within a + /// directory, and pointers to other directories. + fn to_packfile_entries( + &self, + pack_file: &mut IndexMap>, + ) -> Result { + let mut tree = Vec::with_capacity(self.0.len()); + + for (name, item) in &self.0 { + tree.push(match item.as_ref() { + TreeItem::Blob(hash) => LowLevelTreeItem { + kind: TreeItemKind::File, + name, + hash: *hash, + }, + TreeItem::Tree(tree) => LowLevelTreeItem { + kind: TreeItemKind::Directory, + name, + // we're essentially working through our tree from the bottom up, + // so we can grab the hash of each directory along the way and + // reference it from the parent directory + hash: tree.to_packfile_entries(pack_file)?, + }, + }); + } + + // gets the hash of the tree we've just worked on, and + // pushes it to the packfile + let tree = PackFileEntry::Tree(tree); + let hash = tree.hash()?; + pack_file.insert(hash, tree); + + Ok(hash) + } +} + +/// An item within a `Tree`, this could be a file blob or another directory. +#[derive(Debug)] +enum TreeItem<'a> { + /// Refers to a file by hash + Blob(HashOutput), + /// Refers to a nested directory + Tree(Tree<'a>), +} diff --git a/src/protocol/low_level.rs b/src/protocol/low_level.rs new file mode 100644 index 0000000..512e98c --- /dev/null +++ b/src/protocol/low_level.rs @@ -0,0 +1,323 @@ +use bytes::{BufMut, BytesMut}; +use flate2::{write::ZlibEncoder, Compression}; +use sha1::Digest; +use std::{convert::TryInto, fmt::Write, io::Write as IoWrite}; + +pub type HashOutput = [u8; 20]; + +// The packfile itself is a very simple format. There is a header, a +// series of packed objects (each with it's own header and body) and +// then a checksum trailer. The first four bytes is the string 'PACK', +// which is sort of used to make sure you're getting the start of the +// packfile correctly. This is followed by a 4-byte packfile version +// number and then a 4-byte number of entries in that file. +pub struct PackFile<'a> { + entries: Vec>, +} + +impl<'a> PackFile<'a> { + #[must_use] + pub fn new(entries: Vec>) -> Self { + Self { entries } + } + + #[must_use] + pub const fn header_size() -> usize { + "PACK".len() + std::mem::size_of::() + std::mem::size_of::() + } + + #[must_use] + pub const fn footer_size() -> usize { + 20 + } + + pub fn encode_to(&self, original_buf: &mut BytesMut) -> Result<(), anyhow::Error> { + let mut buf = original_buf.split_off(original_buf.len()); + buf.reserve(Self::header_size() + Self::footer_size()); + + // header + buf.extend_from_slice(b"PACK"); // magic header + buf.put_u32(2); // version + buf.put_u32(self.entries.len().try_into()?); // number of entries in the packfile + + // body + for entry in &self.entries { + entry.encode_to(&mut buf)?; + } + + // footer + buf.extend_from_slice(&sha1::Sha1::digest(&buf[..])); + + original_buf.unsplit(buf); + + Ok(()) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Commit<'a> { + pub tree: HashOutput, + // pub parent: [u8; 20], + pub author: CommitUserInfo<'a>, + pub committer: CommitUserInfo<'a>, + // pub gpgsig: &str, + pub message: &'a str, +} + +impl Commit<'_> { + fn encode_to(&self, out: &mut BytesMut) -> Result<(), anyhow::Error> { + let mut tree_hex = [0_u8; 20 * 2]; + hex::encode_to_slice(self.tree, &mut tree_hex)?; + + out.write_str("tree ")?; + out.extend_from_slice(&tree_hex); + out.write_char('\n')?; + + writeln!(out, "author {}", self.author.encode())?; + writeln!(out, "committer {}", self.committer.encode())?; + write!(out, "\n{}", self.message)?; + + Ok(()) + } + + #[must_use] + pub fn size(&self) -> usize { + let mut len = 0; + len += "tree ".len() + (self.tree.len() * 2) + "\n".len(); + len += "author ".len() + self.author.size() + "\n".len(); + len += "committer ".len() + self.committer.size() + "\n".len(); + len += "\n".len() + self.message.len(); + len + } +} + +#[derive(Copy, Clone, Debug)] +pub struct CommitUserInfo<'a> { + pub name: &'a str, + pub email: &'a str, + pub time: time::OffsetDateTime, +} + +impl CommitUserInfo<'_> { + fn encode(&self) -> String { + // TODO: remove `format!`, `format_args!`? + format!( + "{} <{}> {} +0000", + self.name, + self.email, + self.time.unix_timestamp() + ) + } + + #[must_use] + pub fn size(&self) -> usize { + let timestamp_len = itoa::Buffer::new().format(self.time.unix_timestamp()).len(); + + self.name.len() + + "< ".len() + + self.email.len() + + "> ".len() + + timestamp_len + + " +0000".len() + } +} + +#[derive(Debug, Copy, Clone)] +pub enum TreeItemKind { + File, + Directory, +} + +impl TreeItemKind { + #[must_use] + pub const fn mode(&self) -> &'static str { + match self { + Self::File => "100644", + Self::Directory => "40000", + } + } +} + +#[derive(Debug, Copy, Clone)] +pub struct TreeItem<'a> { + pub kind: TreeItemKind, + pub name: &'a str, + pub hash: HashOutput, +} + +// `[mode] [name]\0[hash]` +impl TreeItem<'_> { + fn encode_to(&self, out: &mut BytesMut) -> Result<(), anyhow::Error> { + out.write_str(self.kind.mode())?; + write!(out, " {}\0", self.name)?; + out.extend_from_slice(&self.hash); + Ok(()) + } + + #[must_use] + pub fn size(&self) -> usize { + self.kind.mode().len() + " ".len() + self.name.len() + "\0".len() + self.hash.len() + } +} + +#[derive(Debug, Clone)] // could be copy but Vec> +pub enum PackFileEntry<'a> { + // jordan@Jordans-MacBook-Pro-2 0d % printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - f5/473259d9674ed66239766a013f96a3550374e3 | gzip -dc + // commit 1068tree 0d586b48bc42e8591773d3d8a7223551c39d453c + // parent c2a862612a14346ae95234f26efae1ee69b5b7a9 + // author Jordan Doyle 1630244577 +0100 + // committer Jordan Doyle 1630244577 +0100 + // gpgsig -----BEGIN PGP SIGNATURE----- + // + // iQIzBAABCAAdFiEEMn1zof7yzaURQBGDHqa65vZtxJoFAmErjuEACgkQHqa65vZt + // xJqhvhAAieKXnGRjT926qzozcvarC8D3TlA+Z1wVXueTAWqfusNIP0zCun/crOb2 + // tOULO+/DXVBmwu5eInAf+t/wvlnIsrzJonhVr1ZT0f0vDX6fs2vflWg4UCVEuTsZ + // tg+aTjcibwnmViIM9XVOzhU8Au2OIqMQLyQOMWSt8NhY0W2WhBCdQvhktvK1V8W6 + // omPs04SrR39xWBDQaxsXYxq/1ZKUYXDwudvEfv14EvrxG1vWumpUVJd7Ib5w4gXX + // fYa95DxYL720ZaiWPIYEG8FMBzSOpo6lUzY9g2/o/wKwSQZJNvpaMGCuouy8Fb+E + // UaqC0XPxqpKG9duXPgCldUr+P7++48CF5zc358RBGz5OCNeTREsIQQo5PUO1k+wO + // FnGOQTT8vvNOrxBgb3QgKu67RVwWDc6JnQCNpUrhUJrXMDWnYLBqo4Y+CdKGSQ4G + // hW8V/hVTOlJZNi8bbU4v53cxh4nXiMM6NKUblUKs65ar3/2dkojwunz7r7GVZ6mG + // QUpr9+ybG61XDqd1ad1A/B/i3WdWixTmJS3K/4uXjFjFX1f3RAk7O0gHc9I8HYOE + // Vd8UsHzLOWAUHeaqbsd6xx3GCXF4D5D++kh9OY9Ov7CXlqbYbHd6Atg+PQ7VnqNf + // bDqWN0Q2qcKX3k4ggtucmkkA6gP+K3+F5ANQj3AsGMQeddowC0Y= + // =fXoH + // -----END PGP SIGNATURE----- + // + // test + Commit(Commit<'a>), + // jordan@Jordans-MacBook-Pro-2 0d % printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - 0d/586b48bc42e8591773d3d8a7223551c39d453c | gzip -dc + // tree 20940000 .cargo���CYy��Ve�������100644 .gitignore�K��_ow�]����4�n�ݺ100644 Cargo.lock�7�3-�?/�� + // kt��c0C�100644 Cargo.toml�6�&(��]\8@�SHA�]f40000 src0QW��ƅ���b[�!�S&N�100644 test�G2Y�gN�b9vj?��Ut� + Tree(Vec>), + // jordan@Jordans-MacBook-Pro-2 objects % printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" | cat - f5/473259d9674ed66239766a013f96a3550374e3| gzip -dc + // blob 23try and find me in .git + Blob(&'a [u8]), + // Tag, + // OfsDelta, + // RefDelta, +} + +impl PackFileEntry<'_> { + fn write_header(&self, buf: &mut BytesMut) { + let mut size = self.uncompressed_size(); + + // write header + { + let mut val = 0b1000_0000_u8; + + val |= match self { + Self::Commit(_) => 0b001, + Self::Tree(_) => 0b010, + Self::Blob(_) => 0b011, + // Self::Tag => 0b100, + // Self::OfsDelta => 0b110, + // Self::RefDelta => 0b111, + } << 4; + + // pack the 4 LSBs of the size into the header + #[allow(clippy::cast_possible_truncation)] // value is masked + { + val |= (size & 0b1111) as u8; + } + size >>= 4; + + buf.put_u8(val); + } + + // write size bytes + while size != 0 { + // read 7 LSBs from the `size` and push them off for the next iteration + #[allow(clippy::cast_possible_truncation)] // value is masked + let mut val = (size & 0b111_1111) as u8; + size >>= 7; + + if size != 0 { + // MSB set to 1 implies there's more size bytes to come, otherwise + // the data starts after this byte + val |= 1 << 7; + } + + buf.put_u8(val); + } + } + + pub fn encode_to(&self, original_out: &mut BytesMut) -> Result<(), anyhow::Error> { + self.write_header(original_out); // TODO: this needs space reserving for it + + // todo is there a way to stream through the zlibencoder so we don't have to + // have this intermediate bytesmut and vec? + let mut out = BytesMut::new(); + + let size = self.uncompressed_size(); + original_out.reserve(size); + // the data ends up getting compressed but we'll need at least this many bytes + out.reserve(size); + + match self { + Self::Commit(commit) => { + commit.encode_to(&mut out)?; + } + Self::Tree(items) => { + for item in items { + item.encode_to(&mut out)?; + } + } + Self::Blob(data) => { + out.extend_from_slice(data); + } + } + + debug_assert_eq!(out.len(), size); + + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(&out)?; + let compressed_data = e.finish()?; + + original_out.extend_from_slice(&compressed_data); + + Ok(()) + } + + #[must_use] + pub fn uncompressed_size(&self) -> usize { + match self { + Self::Commit(commit) => commit.size(), + Self::Tree(items) => items.iter().map(TreeItem::size).sum(), + Self::Blob(data) => data.len(), + } + } + + // wen const generics for RustCrypto? :-( + pub fn hash(&self) -> Result { + let size = self.uncompressed_size(); + + let file_prefix = match self { + Self::Commit(_) => "commit", + Self::Tree(_) => "tree", + Self::Blob(_) => "blob", + }; + + let size_len = itoa::Buffer::new().format(size).len(); + + let mut out = + BytesMut::with_capacity(file_prefix.len() + " ".len() + size_len + "\n".len() + size); + + write!(out, "{} {}\0", file_prefix, size)?; + match self { + Self::Commit(commit) => { + commit.encode_to(&mut out)?; + } + Self::Tree(items) => { + for item in items { + item.encode_to(&mut out)?; + } + } + Self::Blob(blob) => { + out.extend_from_slice(blob); + } + } + + Ok(sha1::Sha1::digest(&out).into()) + } +} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs new file mode 100644 index 0000000..ba28159 --- /dev/null +++ b/src/protocol/mod.rs @@ -0,0 +1,4 @@ +pub mod high_level; +pub mod low_level; +pub mod codec; +pub mod packet_line; diff --git a/src/protocol/packet_line.rs b/src/protocol/packet_line.rs new file mode 100644 index 0000000..e469001 --- /dev/null +++ b/src/protocol/packet_line.rs @@ -0,0 +1,71 @@ +use bytes::{BufMut, BytesMut}; +use std::fmt::Write; + +use super::low_level::PackFile; + +/// Every packet sent to the client from us should be a `PktLine`. +pub enum PktLine<'a> { + Data(&'a [u8]), + /// Similar to a data packet, but used during packfile sending to indicate this + /// packet is a block of data by appending a byte containing the u8 `1`. + SidebandData(PackFile<'a>), + /// Similar to a data packet, but used during packfile sending to indicate this + /// packet is a status message by appending a byte containing the u8 `2`. + SidebandMsg(&'a [u8]), + Flush, + Delimiter, + ResponseEnd, +} + +impl PktLine<'_> { + pub fn encode_to(&self, buf: &mut BytesMut) -> Result<(), anyhow::Error> { + match self { + Self::Data(data) => { + write!(buf, "{:04x}", data.len() + 4)?; + buf.extend_from_slice(data); + } + Self::SidebandData(packfile) => { + // split the buf off so the cost of counting the bytes to put in the + // data line prefix is just the cost of `unsplit` (an atomic decrement) + let mut data_buf = buf.split_off(buf.len()); + + data_buf.put_u8(1); // sideband, 1 = data + packfile.encode_to(&mut data_buf)?; + + // write into the buf not the data buf so it's at the start of the msg + write!(buf, "{:04x}", data_buf.len() + 4)?; + buf.unsplit(data_buf); + } + Self::SidebandMsg(msg) => { + write!(buf, "{:04x}", msg.len() + 4 + 1)?; + buf.put_u8(2); // sideband, 2 = msg + buf.extend_from_slice(msg); + } + Self::Flush => buf.extend_from_slice(b"0000"), + Self::Delimiter => buf.extend_from_slice(b"0001"), + Self::ResponseEnd => buf.extend_from_slice(b"0002"), + } + + Ok(()) + } +} + +impl<'a> From<&'a str> for PktLine<'a> { + fn from(val: &'a str) -> Self { + PktLine::Data(val.as_bytes()) + } +} + +#[cfg(test)] +mod test { + use bytes::BytesMut; + + #[test] + fn test_pkt_line() { + let mut buffer = BytesMut::new(); + super::PktLine::Data(b"agent=git/2.32.0\n") + .encode_to(&mut buffer) + .unwrap(); + assert_eq!(buffer.as_ref(), b"0015agent=git/2.32.0\n"); + } +} diff --git a/src/providers/gitlab.rs b/src/providers/gitlab.rs new file mode 100644 index 0000000..49c6508 --- /dev/null +++ b/src/providers/gitlab.rs @@ -0,0 +1,215 @@ +use crate::providers::{Release, User}; +use async_trait::async_trait; +use futures::{stream::FuturesUnordered, StreamExt, TryStreamExt}; +use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; +use reqwest::header; +use serde::Deserialize; +use std::sync::Arc; + +pub struct Gitlab { + client: reqwest::Client, + base_url: String, +} + +impl Gitlab { + pub fn new() -> anyhow::Result { + let mut headers = header::HeaderMap::new(); + headers.insert( + "PRIVATE-TOKEN", + header::HeaderValue::from_static("token"), + ); + + Ok(Self { + client: reqwest::ClientBuilder::new() + .default_headers(headers) + .build()?, + base_url: "https://127.0.0.1/api/v4".to_string(), + }) + } + + pub async fn get_impersonation_token_for(&self, user: &User) -> anyhow::Result { + let impersonation_token: GitlabImpersonationTokenResponse = self + .client + .get(format!( + "{}/users/{}/impersonation_tokens", + self.base_url, user.id + )) + .body(format!("name={};scopes=api", env!("CARGO_PKG_NAME"))) + .send() + .await? + .json() + .await?; + + Ok(impersonation_token.token) + } +} + +#[async_trait] +impl super::UserProvider for Gitlab { + async fn find_user_by_username_password_combo( + &self, + username_password: &str, + ) -> anyhow::Result> { + let mut splitter = username_password.splitn(2, ':'); + let (username, password) = (splitter.next().unwrap(), splitter.next().unwrap()); + + if username == "gitlab-ci-token" { + let res: GitlabJobResponse = self + .client + .get(format!("{}/job", self.base_url)) + .header("JOB-TOKEN", password) + .send() + .await? + .json() + .await?; + + Ok(Some(User { + id: res.user.id, + username: res.user.username, + })) + } else { + Ok(None) + } + } + + async fn find_user_by_ssh_key(&self, fingerprint: &str) -> anyhow::Result> { + let res: GitlabSshKeyLookupResponse = self + .client + .get(format!( + "{}/keys?fingerprint={}", + self.base_url, fingerprint + )) + .send() + .await? + .json() + .await?; + Ok(res.user.map(|u| User { + id: u.id, + username: u.username, + })) + } +} + +#[async_trait] +impl super::PackageProvider for Gitlab { + async fn fetch_releases_for_group( + self: Arc, + group: &str, + do_as: User, + ) -> anyhow::Result> { + let impersonation_token = Arc::new(self.get_impersonation_token_for(&do_as).await?); + + let mut next_uri = Some(format!( + "{}/groups/{}/packages?per_page=100&pagination=keyset&order_by=id&sort=asc&sudo={}", + self.base_url, + utf8_percent_encode(group, NON_ALPHANUMERIC), + do_as.id + )); + + let futures = FuturesUnordered::new(); + + while let Some(uri) = next_uri.take() { + let res = self.client.get(uri).send().await?; + + if let Some(link_header) = res.headers().get(reqwest::header::LINK) { + let mut link_header = parse_link_header::parse_with_rel(link_header.to_str()?)?; + + if let Some(next) = link_header.remove("next") { + next_uri = Some(next.raw_uri); + } + } + + let res: Vec = res.json().await?; + + for release in res { + let this = self.clone(); + let impersonation_token = impersonation_token.clone(); + + futures.push(tokio::spawn(async move { + let (project, package) = { + let mut splitter = release.links.web_path.splitn(2, "/-/packages/"); + match (splitter.next(), splitter.next()) { + (Some(project), Some(package)) => (&project[1..], package), + _ => return Ok(None), + } + }; + + let package_files: GitlabPackageFilesResponse = this + .client + .get(format!( + "{}/projects/{}/packages/{}/package_files", + this.base_url, + utf8_percent_encode(project, NON_ALPHANUMERIC), + utf8_percent_encode(package, NON_ALPHANUMERIC), + )) + .send() + .await? + .json() + .await?; + + Ok::<_, anyhow::Error>(Some(Release { + uri: format!( + "{}/projects/{}/packages/generic/{}/{}/{}?private_token={}", + this.base_url, + utf8_percent_encode(project, NON_ALPHANUMERIC), + utf8_percent_encode(&release.name, NON_ALPHANUMERIC), + utf8_percent_encode(&release.version, NON_ALPHANUMERIC), + package_files.file_name, + impersonation_token, + ), + name: release.name, + version: release.version, + checksum: package_files.file_sha256, + })) + })) + } + } + + futures + .err_into() + .filter_map(|v| async move { v.and_then(|v| v).transpose() }) + .try_collect() + .await + } +} + +#[derive(Deserialize)] +pub struct GitlabImpersonationTokenResponse { + pub token: String, +} + +#[derive(Deserialize)] +pub struct GitlabPackageFilesResponse { + pub file_name: String, + pub file_sha256: String, +} + +#[derive(Deserialize)] +pub struct GitlabPackageResponse { + pub id: u64, + pub name: String, + pub version: String, + #[serde(rename = "_links")] + pub links: GitlabPackageLinksResponse, +} + +#[derive(Deserialize)] +pub struct GitlabPackageLinksResponse { + web_path: String, +} + +#[derive(Deserialize)] +pub struct GitlabJobResponse { + pub user: GitlabUserResponse, +} + +#[derive(Deserialize)] +pub struct GitlabSshKeyLookupResponse { + pub user: Option, +} + +#[derive(Deserialize)] +pub struct GitlabUserResponse { + pub id: u64, + pub username: String, +} diff --git a/src/providers/mod.rs b/src/providers/mod.rs new file mode 100644 index 0000000..05bb127 --- /dev/null +++ b/src/providers/mod.rs @@ -0,0 +1,37 @@ +pub mod gitlab; + +use async_trait::async_trait; +use std::sync::Arc; + +#[async_trait] +pub trait UserProvider { + async fn find_user_by_username_password_combo( + &self, + username_password: &str, + ) -> anyhow::Result>; + + async fn find_user_by_ssh_key(&self, fingerprint: &str) -> anyhow::Result>; +} + +#[async_trait] +pub trait PackageProvider { + async fn fetch_releases_for_group( + self: Arc, + group: &str, + do_as: User, + ) -> anyhow::Result>; +} + +#[derive(Debug, Clone)] +pub struct User { + pub id: u64, + pub username: String, +} + +#[derive(Debug)] +pub struct Release { + pub name: String, + pub version: String, + pub checksum: String, + pub uri: String, +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..b01f71c --- /dev/null +++ b/src/util.rs @@ -0,0 +1,18 @@ +/// Retrieves the key fingerprint, encoded in hex and separated in two character chunks +/// with colons. +pub fn format_fingerprint(fingerprint: &str) -> Result { + let raw_hex = hex::encode( + base64::decode(&fingerprint).map_err(|_| thrussh_keys::Error::CouldNotReadKey)?, + ); + let mut hex = String::with_capacity(raw_hex.len() + (raw_hex.len() / 2 - 1)); + + for (i, c) in raw_hex.chars().enumerate() { + if i != 0 && i % 2 == 0 { + hex.push(':'); + } + + hex.push(c); + } + + Ok(hex) +} -- libgit2 1.7.2