├── .github ├── images │ └── demo.png └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── app_config.rs ├── app_tracing.rs ├── auth.rs ├── download.rs ├── github │ ├── cache.rs │ └── mod.rs ├── lib.rs ├── main.rs ├── middleware.rs ├── oauth │ ├── github.rs │ ├── mod.rs │ ├── shared_pat.rs │ ├── types.rs │ └── utils.rs ├── provider_registry.rs └── signature.rs └── tests ├── main.tf └── routes.rs /.github/images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattclement/tfreg/2d0ea96b2aef6ca334446c9175c4960041575ce3/.github/images/demo.png -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | test: 13 | name: Format, Lint, Test 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - name: Install Protoc 19 | uses: arduino/setup-protoc@v3 20 | 21 | - uses: actions/cache@v4 22 | with: 23 | path: | 24 | ~/.cargo/bin/ 25 | ~/.cargo/registry/index/ 26 | ~/.cargo/registry/cache/ 27 | ~/.cargo/git/db/ 28 | target/ 29 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/Cargo.toml') }} 30 | 31 | - name: Install stable toolchain 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | components: clippy, rustfmt 35 | 36 | - name: Run cargo fmt 37 | run: cargo fmt --all -- --check 38 | 39 | - name: Run cargo clippy 40 | run: cargo clippy -- -D warnings 41 | 42 | - name: Run cargo test 43 | run: cargo test --all-features 44 | env: 45 | TFREG_CLIENT_ID: 'no' 46 | TFREG_CLIENT_SECRET: 'no' 47 | TFREG_SECRET_KEY: 'no' 48 | TFREG_TEST_PAT: '${{ secrets.GITHUB_TOKEN }}' 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | build: 13 | name: build ${{ matrix.target }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | include: 18 | - target: aarch64-apple-darwin 19 | os: macos-latest 20 | - target: x86_64-apple-darwin 21 | os: macos-latest 22 | - target: x86_64-unknown-linux-gnu 23 | os: ubuntu-latest 24 | - target: x86_64-unknown-linux-musl 25 | os: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Install Protoc 30 | uses: arduino/setup-protoc@v3 31 | 32 | - uses: actions/cache@v4 33 | with: 34 | path: | 35 | ~/.cargo/bin/ 36 | ~/.cargo/registry/index/ 37 | ~/.cargo/registry/cache/ 38 | ~/.cargo/git/db/ 39 | target/ 40 | key: ${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/Cargo.toml') }} 41 | 42 | - name: Add rustup target 43 | if: matrix.target != 'x86_64-unknown-linux-musl' 44 | run: rustup target add ${{ matrix.target }} 45 | 46 | - name: Build ${{ matrix.target }} 47 | if: matrix.target != 'x86_64-unknown-linux-musl' 48 | run: cargo build --release --target=${{ matrix.target }} 49 | 50 | - name: Build static ${{ matrix.target }} 51 | if: matrix.target == 'x86_64-unknown-linux-musl' 52 | run: | 53 | mkdir -p ~/.cargo/{git,registry} 54 | docker run --rm -t \ 55 | --mount type=bind,source=${{ github.workspace }},target=/volume \ 56 | --mount type=bind,source=$HOME/.cargo/registry,target=/root/.cargo/registry \ 57 | --mount type=bind,source=$HOME/.cargo/git,target=/root/.cargo/git \ 58 | clux/muslrust:stable \ 59 | cargo build --release 60 | 61 | - name: Create tar.gz 62 | run: tar -czvf tfreg_${{ matrix.target }}.tar.gz -C target/${{ matrix.target }}/release tfreg 63 | 64 | - name: Upload artifacts 65 | uses: actions/upload-artifact@v1 66 | with: 67 | name: tfreg-${{ matrix.target }} 68 | path: tfreg_${{ matrix.target }}.tar.gz 69 | 70 | release: 71 | name: release 72 | needs: 73 | - build 74 | runs-on: ubuntu-latest 75 | steps: 76 | - uses: actions/download-artifact@v3 77 | with: 78 | path: tmp 79 | 80 | - name: Prepare release directory 81 | run: mkdir bin && cp tmp/**/*.tar.gz bin && ls bin 82 | 83 | - name: Calculate SHA256SUMS 84 | run: pushd bin && sha256sum * > SHA256SUMS && popd 85 | 86 | - name: Create github release 87 | uses: softprops/action-gh-release@v1 88 | with: 89 | files: bin/* 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .env 3 | cache 4 | .terraform* 5 | config.toml 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.2.0] 9 | 10 | ### Changed 11 | - **Fix typos: `OLTP` should be `OTLP`. This is a breaking change.** 12 | - Updated dependencies 13 | 14 | ## [0.1.2] - 2023-05-11 15 | 16 | ### Changed 17 | - Updated dependencies 18 | 19 | ## [0.1.1] - 2022-07-05 20 | 21 | ### Changed 22 | - Use octocrab rustls feature to avoid openssl dependency. 23 | 24 | ## [0.1.0] - 2022-07-02 25 | 26 | ### Added 27 | - Initial release 28 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aead" 22 | version = "0.5.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 25 | dependencies = [ 26 | "crypto-common", 27 | "generic-array 0.14.7", 28 | ] 29 | 30 | [[package]] 31 | name = "aes" 32 | version = "0.8.4" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 35 | dependencies = [ 36 | "cfg-if", 37 | "cipher", 38 | "cpufeatures", 39 | "zeroize", 40 | ] 41 | 42 | [[package]] 43 | name = "aes-gcm" 44 | version = "0.10.3" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" 47 | dependencies = [ 48 | "aead", 49 | "aes", 50 | "cipher", 51 | "ctr", 52 | "ghash", 53 | "subtle", 54 | ] 55 | 56 | [[package]] 57 | name = "aho-corasick" 58 | version = "1.1.2" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 61 | dependencies = [ 62 | "memchr", 63 | ] 64 | 65 | [[package]] 66 | name = "alloc-no-stdlib" 67 | version = "2.0.4" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 70 | 71 | [[package]] 72 | name = "alloc-stdlib" 73 | version = "0.2.2" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 76 | dependencies = [ 77 | "alloc-no-stdlib", 78 | ] 79 | 80 | [[package]] 81 | name = "android-tzdata" 82 | version = "0.1.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 85 | 86 | [[package]] 87 | name = "android_system_properties" 88 | version = "0.1.5" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 91 | dependencies = [ 92 | "libc", 93 | ] 94 | 95 | [[package]] 96 | name = "anyhow" 97 | version = "1.0.80" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" 100 | 101 | [[package]] 102 | name = "arc-swap" 103 | version = "1.6.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" 106 | 107 | [[package]] 108 | name = "ascii-canvas" 109 | version = "3.0.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 112 | dependencies = [ 113 | "term", 114 | ] 115 | 116 | [[package]] 117 | name = "async-compression" 118 | version = "0.4.6" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" 121 | dependencies = [ 122 | "brotli", 123 | "flate2", 124 | "futures-core", 125 | "memchr", 126 | "pin-project-lite", 127 | "tokio", 128 | "zstd", 129 | "zstd-safe", 130 | ] 131 | 132 | [[package]] 133 | name = "async-trait" 134 | version = "0.1.77" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" 137 | dependencies = [ 138 | "proc-macro2", 139 | "quote", 140 | "syn 2.0.52", 141 | ] 142 | 143 | [[package]] 144 | name = "atty" 145 | version = "0.2.14" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 148 | dependencies = [ 149 | "hermit-abi 0.1.19", 150 | "libc", 151 | "winapi", 152 | ] 153 | 154 | [[package]] 155 | name = "autocfg" 156 | version = "1.1.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 159 | 160 | [[package]] 161 | name = "axum" 162 | version = "0.6.20" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" 165 | dependencies = [ 166 | "async-trait", 167 | "axum-core 0.3.4", 168 | "bitflags 1.3.2", 169 | "bytes", 170 | "futures-util", 171 | "http 0.2.11", 172 | "http-body 0.4.6", 173 | "hyper 0.14.28", 174 | "itoa", 175 | "matchit", 176 | "memchr", 177 | "mime", 178 | "percent-encoding", 179 | "pin-project-lite", 180 | "rustversion", 181 | "serde", 182 | "sync_wrapper", 183 | "tower", 184 | "tower-layer", 185 | "tower-service", 186 | ] 187 | 188 | [[package]] 189 | name = "axum" 190 | version = "0.7.4" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" 193 | dependencies = [ 194 | "async-trait", 195 | "axum-core 0.4.3", 196 | "bytes", 197 | "futures-util", 198 | "http 1.0.0", 199 | "http-body 1.0.0", 200 | "http-body-util", 201 | "hyper 1.2.0", 202 | "hyper-util", 203 | "itoa", 204 | "matchit", 205 | "memchr", 206 | "mime", 207 | "percent-encoding", 208 | "pin-project-lite", 209 | "rustversion", 210 | "serde", 211 | "serde_json", 212 | "serde_path_to_error", 213 | "serde_urlencoded", 214 | "sync_wrapper", 215 | "tokio", 216 | "tower", 217 | "tower-layer", 218 | "tower-service", 219 | "tracing", 220 | ] 221 | 222 | [[package]] 223 | name = "axum-core" 224 | version = "0.3.4" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" 227 | dependencies = [ 228 | "async-trait", 229 | "bytes", 230 | "futures-util", 231 | "http 0.2.11", 232 | "http-body 0.4.6", 233 | "mime", 234 | "rustversion", 235 | "tower-layer", 236 | "tower-service", 237 | ] 238 | 239 | [[package]] 240 | name = "axum-core" 241 | version = "0.4.3" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" 244 | dependencies = [ 245 | "async-trait", 246 | "bytes", 247 | "futures-util", 248 | "http 1.0.0", 249 | "http-body 1.0.0", 250 | "http-body-util", 251 | "mime", 252 | "pin-project-lite", 253 | "rustversion", 254 | "sync_wrapper", 255 | "tower-layer", 256 | "tower-service", 257 | "tracing", 258 | ] 259 | 260 | [[package]] 261 | name = "backtrace" 262 | version = "0.3.69" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 265 | dependencies = [ 266 | "addr2line", 267 | "cc", 268 | "cfg-if", 269 | "libc", 270 | "miniz_oxide", 271 | "object", 272 | "rustc-demangle", 273 | ] 274 | 275 | [[package]] 276 | name = "base16ct" 277 | version = "0.2.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 280 | 281 | [[package]] 282 | name = "base64" 283 | version = "0.13.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 286 | 287 | [[package]] 288 | name = "base64" 289 | version = "0.21.7" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 292 | 293 | [[package]] 294 | name = "base64ct" 295 | version = "1.6.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 298 | 299 | [[package]] 300 | name = "bit-set" 301 | version = "0.5.3" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 304 | dependencies = [ 305 | "bit-vec", 306 | ] 307 | 308 | [[package]] 309 | name = "bit-vec" 310 | version = "0.6.3" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 313 | 314 | [[package]] 315 | name = "bitflags" 316 | version = "1.3.2" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 319 | 320 | [[package]] 321 | name = "bitflags" 322 | version = "2.4.2" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 325 | 326 | [[package]] 327 | name = "block-buffer" 328 | version = "0.10.4" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 331 | dependencies = [ 332 | "generic-array 0.14.7", 333 | ] 334 | 335 | [[package]] 336 | name = "block-padding" 337 | version = "0.3.3" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" 340 | dependencies = [ 341 | "generic-array 0.14.7", 342 | ] 343 | 344 | [[package]] 345 | name = "blowfish" 346 | version = "0.9.1" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" 349 | dependencies = [ 350 | "byteorder", 351 | "cipher", 352 | ] 353 | 354 | [[package]] 355 | name = "brotli" 356 | version = "3.4.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" 359 | dependencies = [ 360 | "alloc-no-stdlib", 361 | "alloc-stdlib", 362 | "brotli-decompressor", 363 | ] 364 | 365 | [[package]] 366 | name = "brotli-decompressor" 367 | version = "2.5.1" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" 370 | dependencies = [ 371 | "alloc-no-stdlib", 372 | "alloc-stdlib", 373 | ] 374 | 375 | [[package]] 376 | name = "buffered-reader" 377 | version = "1.3.0" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "2b9b0a25eb06e83579bc985d836e1e3b957a7201301b48538764d2b2e78090d4" 380 | dependencies = [ 381 | "lazy_static", 382 | "libc", 383 | ] 384 | 385 | [[package]] 386 | name = "bumpalo" 387 | version = "3.15.3" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" 390 | 391 | [[package]] 392 | name = "byteorder" 393 | version = "1.5.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 396 | 397 | [[package]] 398 | name = "bytes" 399 | version = "1.5.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" 402 | 403 | [[package]] 404 | name = "camellia" 405 | version = "0.1.0" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" 408 | dependencies = [ 409 | "byteorder", 410 | "cipher", 411 | ] 412 | 413 | [[package]] 414 | name = "cast5" 415 | version = "0.11.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" 418 | dependencies = [ 419 | "cipher", 420 | ] 421 | 422 | [[package]] 423 | name = "cc" 424 | version = "1.0.88" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" 427 | dependencies = [ 428 | "libc", 429 | ] 430 | 431 | [[package]] 432 | name = "cfb-mode" 433 | version = "0.8.2" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" 436 | dependencies = [ 437 | "cipher", 438 | ] 439 | 440 | [[package]] 441 | name = "cfg-if" 442 | version = "1.0.0" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 445 | 446 | [[package]] 447 | name = "chrono" 448 | version = "0.4.34" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" 451 | dependencies = [ 452 | "android-tzdata", 453 | "iana-time-zone", 454 | "js-sys", 455 | "num-traits", 456 | "serde", 457 | "wasm-bindgen", 458 | "windows-targets 0.52.4", 459 | ] 460 | 461 | [[package]] 462 | name = "cipher" 463 | version = "0.4.4" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 466 | dependencies = [ 467 | "crypto-common", 468 | "inout", 469 | "zeroize", 470 | ] 471 | 472 | [[package]] 473 | name = "clap" 474 | version = "3.2.25" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 477 | dependencies = [ 478 | "atty", 479 | "bitflags 1.3.2", 480 | "clap_derive", 481 | "clap_lex", 482 | "indexmap 1.9.3", 483 | "once_cell", 484 | "strsim", 485 | "termcolor", 486 | "textwrap", 487 | ] 488 | 489 | [[package]] 490 | name = "clap_derive" 491 | version = "3.2.25" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" 494 | dependencies = [ 495 | "heck", 496 | "proc-macro-error", 497 | "proc-macro2", 498 | "quote", 499 | "syn 1.0.109", 500 | ] 501 | 502 | [[package]] 503 | name = "clap_lex" 504 | version = "0.2.4" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 507 | dependencies = [ 508 | "os_str_bytes", 509 | ] 510 | 511 | [[package]] 512 | name = "cmac" 513 | version = "0.7.2" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" 516 | dependencies = [ 517 | "cipher", 518 | "dbl", 519 | "digest", 520 | ] 521 | 522 | [[package]] 523 | name = "const-oid" 524 | version = "0.9.6" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 527 | 528 | [[package]] 529 | name = "core-foundation" 530 | version = "0.9.4" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 533 | dependencies = [ 534 | "core-foundation-sys", 535 | "libc", 536 | ] 537 | 538 | [[package]] 539 | name = "core-foundation-sys" 540 | version = "0.8.6" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" 543 | 544 | [[package]] 545 | name = "cpufeatures" 546 | version = "0.2.12" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 549 | dependencies = [ 550 | "libc", 551 | ] 552 | 553 | [[package]] 554 | name = "crc32fast" 555 | version = "1.4.0" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" 558 | dependencies = [ 559 | "cfg-if", 560 | ] 561 | 562 | [[package]] 563 | name = "crossbeam-channel" 564 | version = "0.5.12" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" 567 | dependencies = [ 568 | "crossbeam-utils", 569 | ] 570 | 571 | [[package]] 572 | name = "crossbeam-utils" 573 | version = "0.8.19" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" 576 | 577 | [[package]] 578 | name = "crunchy" 579 | version = "0.2.2" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 582 | 583 | [[package]] 584 | name = "crypto-bigint" 585 | version = "0.5.5" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 588 | dependencies = [ 589 | "generic-array 0.14.7", 590 | "rand_core", 591 | "subtle", 592 | "zeroize", 593 | ] 594 | 595 | [[package]] 596 | name = "crypto-common" 597 | version = "0.1.6" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 600 | dependencies = [ 601 | "generic-array 0.14.7", 602 | "rand_core", 603 | "typenum", 604 | ] 605 | 606 | [[package]] 607 | name = "ct-codecs" 608 | version = "1.1.1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" 611 | 612 | [[package]] 613 | name = "ctr" 614 | version = "0.9.2" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" 617 | dependencies = [ 618 | "cipher", 619 | ] 620 | 621 | [[package]] 622 | name = "curve25519-dalek" 623 | version = "4.1.2" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" 626 | dependencies = [ 627 | "cfg-if", 628 | "cpufeatures", 629 | "curve25519-dalek-derive", 630 | "digest", 631 | "fiat-crypto", 632 | "platforms", 633 | "rustc_version", 634 | "subtle", 635 | "zeroize", 636 | ] 637 | 638 | [[package]] 639 | name = "curve25519-dalek-derive" 640 | version = "0.1.1" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" 643 | dependencies = [ 644 | "proc-macro2", 645 | "quote", 646 | "syn 2.0.52", 647 | ] 648 | 649 | [[package]] 650 | name = "dbl" 651 | version = "0.3.2" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" 654 | dependencies = [ 655 | "generic-array 0.14.7", 656 | ] 657 | 658 | [[package]] 659 | name = "der" 660 | version = "0.7.8" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" 663 | dependencies = [ 664 | "const-oid", 665 | "pem-rfc7468", 666 | "zeroize", 667 | ] 668 | 669 | [[package]] 670 | name = "deranged" 671 | version = "0.3.11" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 674 | dependencies = [ 675 | "powerfmt", 676 | ] 677 | 678 | [[package]] 679 | name = "des" 680 | version = "0.8.1" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" 683 | dependencies = [ 684 | "cipher", 685 | ] 686 | 687 | [[package]] 688 | name = "digest" 689 | version = "0.10.7" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 692 | dependencies = [ 693 | "block-buffer", 694 | "const-oid", 695 | "crypto-common", 696 | "subtle", 697 | ] 698 | 699 | [[package]] 700 | name = "dirs-next" 701 | version = "2.0.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 704 | dependencies = [ 705 | "cfg-if", 706 | "dirs-sys-next", 707 | ] 708 | 709 | [[package]] 710 | name = "dirs-sys-next" 711 | version = "0.1.2" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 714 | dependencies = [ 715 | "libc", 716 | "redox_users", 717 | "winapi", 718 | ] 719 | 720 | [[package]] 721 | name = "doc-comment" 722 | version = "0.3.3" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 725 | 726 | [[package]] 727 | name = "dsa" 728 | version = "0.6.3" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" 731 | dependencies = [ 732 | "digest", 733 | "num-bigint-dig", 734 | "num-traits", 735 | "pkcs8", 736 | "rfc6979", 737 | "sha2", 738 | "signature", 739 | "zeroize", 740 | ] 741 | 742 | [[package]] 743 | name = "dyn-clone" 744 | version = "1.0.17" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" 747 | 748 | [[package]] 749 | name = "eax" 750 | version = "0.5.0" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" 753 | dependencies = [ 754 | "aead", 755 | "cipher", 756 | "cmac", 757 | "ctr", 758 | "subtle", 759 | ] 760 | 761 | [[package]] 762 | name = "ecb" 763 | version = "0.1.2" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" 766 | dependencies = [ 767 | "cipher", 768 | ] 769 | 770 | [[package]] 771 | name = "ecdsa" 772 | version = "0.16.9" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 775 | dependencies = [ 776 | "der", 777 | "digest", 778 | "elliptic-curve", 779 | "rfc6979", 780 | "signature", 781 | "spki", 782 | ] 783 | 784 | [[package]] 785 | name = "ed25519" 786 | version = "2.2.3" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" 789 | dependencies = [ 790 | "pkcs8", 791 | "signature", 792 | ] 793 | 794 | [[package]] 795 | name = "ed25519-dalek" 796 | version = "2.1.1" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" 799 | dependencies = [ 800 | "curve25519-dalek", 801 | "ed25519", 802 | "rand_core", 803 | "serde", 804 | "sha2", 805 | "subtle", 806 | "zeroize", 807 | ] 808 | 809 | [[package]] 810 | name = "either" 811 | version = "1.10.0" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" 814 | 815 | [[package]] 816 | name = "elliptic-curve" 817 | version = "0.13.8" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 820 | dependencies = [ 821 | "base16ct", 822 | "crypto-bigint", 823 | "digest", 824 | "ff", 825 | "generic-array 0.14.7", 826 | "group", 827 | "hkdf", 828 | "pem-rfc7468", 829 | "pkcs8", 830 | "rand_core", 831 | "sec1", 832 | "subtle", 833 | "zeroize", 834 | ] 835 | 836 | [[package]] 837 | name = "ena" 838 | version = "0.14.2" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" 841 | dependencies = [ 842 | "log", 843 | ] 844 | 845 | [[package]] 846 | name = "encoding_rs" 847 | version = "0.8.33" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" 850 | dependencies = [ 851 | "cfg-if", 852 | ] 853 | 854 | [[package]] 855 | name = "equivalent" 856 | version = "1.0.1" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 859 | 860 | [[package]] 861 | name = "ff" 862 | version = "0.13.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" 865 | dependencies = [ 866 | "rand_core", 867 | "subtle", 868 | ] 869 | 870 | [[package]] 871 | name = "fiat-crypto" 872 | version = "0.2.6" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" 875 | 876 | [[package]] 877 | name = "fixedbitset" 878 | version = "0.4.2" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 881 | 882 | [[package]] 883 | name = "flate2" 884 | version = "1.0.28" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" 887 | dependencies = [ 888 | "crc32fast", 889 | "miniz_oxide", 890 | ] 891 | 892 | [[package]] 893 | name = "fnv" 894 | version = "1.0.7" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 897 | 898 | [[package]] 899 | name = "form_urlencoded" 900 | version = "1.2.1" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 903 | dependencies = [ 904 | "percent-encoding", 905 | ] 906 | 907 | [[package]] 908 | name = "futures" 909 | version = "0.3.30" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 912 | dependencies = [ 913 | "futures-channel", 914 | "futures-core", 915 | "futures-executor", 916 | "futures-io", 917 | "futures-sink", 918 | "futures-task", 919 | "futures-util", 920 | ] 921 | 922 | [[package]] 923 | name = "futures-channel" 924 | version = "0.3.30" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 927 | dependencies = [ 928 | "futures-core", 929 | "futures-sink", 930 | ] 931 | 932 | [[package]] 933 | name = "futures-core" 934 | version = "0.3.30" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 937 | 938 | [[package]] 939 | name = "futures-executor" 940 | version = "0.3.30" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 943 | dependencies = [ 944 | "futures-core", 945 | "futures-task", 946 | "futures-util", 947 | ] 948 | 949 | [[package]] 950 | name = "futures-io" 951 | version = "0.3.30" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 954 | 955 | [[package]] 956 | name = "futures-macro" 957 | version = "0.3.30" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 960 | dependencies = [ 961 | "proc-macro2", 962 | "quote", 963 | "syn 2.0.52", 964 | ] 965 | 966 | [[package]] 967 | name = "futures-sink" 968 | version = "0.3.30" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 971 | 972 | [[package]] 973 | name = "futures-task" 974 | version = "0.3.30" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 977 | 978 | [[package]] 979 | name = "futures-util" 980 | version = "0.3.30" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 983 | dependencies = [ 984 | "futures-channel", 985 | "futures-core", 986 | "futures-io", 987 | "futures-macro", 988 | "futures-sink", 989 | "futures-task", 990 | "memchr", 991 | "pin-project-lite", 992 | "pin-utils", 993 | "slab", 994 | ] 995 | 996 | [[package]] 997 | name = "generic-array" 998 | version = "0.14.7" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 1001 | dependencies = [ 1002 | "typenum", 1003 | "version_check", 1004 | "zeroize", 1005 | ] 1006 | 1007 | [[package]] 1008 | name = "generic-array" 1009 | version = "1.0.0" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "fe739944a5406424e080edccb6add95685130b9f160d5407c639c7df0c5836b0" 1012 | dependencies = [ 1013 | "typenum", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "getrandom" 1018 | version = "0.2.12" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" 1021 | dependencies = [ 1022 | "cfg-if", 1023 | "js-sys", 1024 | "libc", 1025 | "wasi", 1026 | "wasm-bindgen", 1027 | ] 1028 | 1029 | [[package]] 1030 | name = "ghash" 1031 | version = "0.5.0" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" 1034 | dependencies = [ 1035 | "opaque-debug", 1036 | "polyval", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "gimli" 1041 | version = "0.28.1" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 1044 | 1045 | [[package]] 1046 | name = "glob" 1047 | version = "0.3.1" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 1050 | 1051 | [[package]] 1052 | name = "group" 1053 | version = "0.13.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 1056 | dependencies = [ 1057 | "ff", 1058 | "rand_core", 1059 | "subtle", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "h2" 1064 | version = "0.3.24" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" 1067 | dependencies = [ 1068 | "bytes", 1069 | "fnv", 1070 | "futures-core", 1071 | "futures-sink", 1072 | "futures-util", 1073 | "http 0.2.11", 1074 | "indexmap 2.2.5", 1075 | "slab", 1076 | "tokio", 1077 | "tokio-util", 1078 | "tracing", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "h2" 1083 | version = "0.4.2" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" 1086 | dependencies = [ 1087 | "bytes", 1088 | "fnv", 1089 | "futures-core", 1090 | "futures-sink", 1091 | "futures-util", 1092 | "http 1.0.0", 1093 | "indexmap 2.2.5", 1094 | "slab", 1095 | "tokio", 1096 | "tokio-util", 1097 | "tracing", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "hashbrown" 1102 | version = "0.12.3" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 1105 | 1106 | [[package]] 1107 | name = "hashbrown" 1108 | version = "0.14.3" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 1111 | 1112 | [[package]] 1113 | name = "heck" 1114 | version = "0.4.1" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1117 | 1118 | [[package]] 1119 | name = "hermit-abi" 1120 | version = "0.1.19" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 1123 | dependencies = [ 1124 | "libc", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "hermit-abi" 1129 | version = "0.3.9" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 1132 | 1133 | [[package]] 1134 | name = "hex" 1135 | version = "0.4.3" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1138 | 1139 | [[package]] 1140 | name = "hkdf" 1141 | version = "0.12.4" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 1144 | dependencies = [ 1145 | "hmac", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "hmac" 1150 | version = "0.12.1" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1153 | dependencies = [ 1154 | "digest", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "http" 1159 | version = "0.2.11" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" 1162 | dependencies = [ 1163 | "bytes", 1164 | "fnv", 1165 | "itoa", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "http" 1170 | version = "1.0.0" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" 1173 | dependencies = [ 1174 | "bytes", 1175 | "fnv", 1176 | "itoa", 1177 | ] 1178 | 1179 | [[package]] 1180 | name = "http-body" 1181 | version = "0.4.6" 1182 | source = "registry+https://github.com/rust-lang/crates.io-index" 1183 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 1184 | dependencies = [ 1185 | "bytes", 1186 | "http 0.2.11", 1187 | "pin-project-lite", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "http-body" 1192 | version = "1.0.0" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 1195 | dependencies = [ 1196 | "bytes", 1197 | "http 1.0.0", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "http-body-util" 1202 | version = "0.1.0" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" 1205 | dependencies = [ 1206 | "bytes", 1207 | "futures-util", 1208 | "http 1.0.0", 1209 | "http-body 1.0.0", 1210 | "pin-project-lite", 1211 | ] 1212 | 1213 | [[package]] 1214 | name = "http-range-header" 1215 | version = "0.4.0" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe" 1218 | 1219 | [[package]] 1220 | name = "httparse" 1221 | version = "1.8.0" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 1224 | 1225 | [[package]] 1226 | name = "httpdate" 1227 | version = "1.0.3" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1230 | 1231 | [[package]] 1232 | name = "hyper" 1233 | version = "0.14.28" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" 1236 | dependencies = [ 1237 | "bytes", 1238 | "futures-channel", 1239 | "futures-core", 1240 | "futures-util", 1241 | "h2 0.3.24", 1242 | "http 0.2.11", 1243 | "http-body 0.4.6", 1244 | "httparse", 1245 | "httpdate", 1246 | "itoa", 1247 | "pin-project-lite", 1248 | "socket2", 1249 | "tokio", 1250 | "tower-service", 1251 | "tracing", 1252 | "want", 1253 | ] 1254 | 1255 | [[package]] 1256 | name = "hyper" 1257 | version = "1.2.0" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" 1260 | dependencies = [ 1261 | "bytes", 1262 | "futures-channel", 1263 | "futures-util", 1264 | "h2 0.4.2", 1265 | "http 1.0.0", 1266 | "http-body 1.0.0", 1267 | "httparse", 1268 | "httpdate", 1269 | "itoa", 1270 | "pin-project-lite", 1271 | "smallvec", 1272 | "tokio", 1273 | "want", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "hyper-rustls" 1278 | version = "0.24.2" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 1281 | dependencies = [ 1282 | "futures-util", 1283 | "http 0.2.11", 1284 | "hyper 0.14.28", 1285 | "rustls 0.21.10", 1286 | "tokio", 1287 | "tokio-rustls 0.24.1", 1288 | ] 1289 | 1290 | [[package]] 1291 | name = "hyper-rustls" 1292 | version = "0.26.0" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" 1295 | dependencies = [ 1296 | "futures-util", 1297 | "http 1.0.0", 1298 | "hyper 1.2.0", 1299 | "hyper-util", 1300 | "log", 1301 | "rustls 0.22.2", 1302 | "rustls-native-certs", 1303 | "rustls-pki-types", 1304 | "tokio", 1305 | "tokio-rustls 0.25.0", 1306 | "tower-service", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "hyper-timeout" 1311 | version = "0.4.1" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" 1314 | dependencies = [ 1315 | "hyper 0.14.28", 1316 | "pin-project-lite", 1317 | "tokio", 1318 | "tokio-io-timeout", 1319 | ] 1320 | 1321 | [[package]] 1322 | name = "hyper-timeout" 1323 | version = "0.5.1" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" 1326 | dependencies = [ 1327 | "hyper 1.2.0", 1328 | "hyper-util", 1329 | "pin-project-lite", 1330 | "tokio", 1331 | "tower-service", 1332 | ] 1333 | 1334 | [[package]] 1335 | name = "hyper-util" 1336 | version = "0.1.3" 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" 1338 | checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" 1339 | dependencies = [ 1340 | "bytes", 1341 | "futures-channel", 1342 | "futures-util", 1343 | "http 1.0.0", 1344 | "http-body 1.0.0", 1345 | "hyper 1.2.0", 1346 | "pin-project-lite", 1347 | "socket2", 1348 | "tokio", 1349 | "tower", 1350 | "tower-service", 1351 | "tracing", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "iana-time-zone" 1356 | version = "0.1.60" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 1359 | dependencies = [ 1360 | "android_system_properties", 1361 | "core-foundation-sys", 1362 | "iana-time-zone-haiku", 1363 | "js-sys", 1364 | "wasm-bindgen", 1365 | "windows-core", 1366 | ] 1367 | 1368 | [[package]] 1369 | name = "iana-time-zone-haiku" 1370 | version = "0.1.2" 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" 1372 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1373 | dependencies = [ 1374 | "cc", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "idea" 1379 | version = "0.5.1" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" 1382 | dependencies = [ 1383 | "cipher", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "idna" 1388 | version = "0.5.0" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 1391 | dependencies = [ 1392 | "unicode-bidi", 1393 | "unicode-normalization", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "indexmap" 1398 | version = "1.9.3" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 1401 | dependencies = [ 1402 | "autocfg", 1403 | "hashbrown 0.12.3", 1404 | ] 1405 | 1406 | [[package]] 1407 | name = "indexmap" 1408 | version = "2.2.5" 1409 | source = "registry+https://github.com/rust-lang/crates.io-index" 1410 | checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" 1411 | dependencies = [ 1412 | "equivalent", 1413 | "hashbrown 0.14.3", 1414 | ] 1415 | 1416 | [[package]] 1417 | name = "inout" 1418 | version = "0.1.3" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 1421 | dependencies = [ 1422 | "block-padding", 1423 | "generic-array 0.14.7", 1424 | ] 1425 | 1426 | [[package]] 1427 | name = "ipnet" 1428 | version = "2.9.0" 1429 | source = "registry+https://github.com/rust-lang/crates.io-index" 1430 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 1431 | 1432 | [[package]] 1433 | name = "iri-string" 1434 | version = "0.7.0" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" 1437 | dependencies = [ 1438 | "memchr", 1439 | "serde", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "itertools" 1444 | version = "0.10.5" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 1447 | dependencies = [ 1448 | "either", 1449 | ] 1450 | 1451 | [[package]] 1452 | name = "itertools" 1453 | version = "0.11.0" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" 1456 | dependencies = [ 1457 | "either", 1458 | ] 1459 | 1460 | [[package]] 1461 | name = "itoa" 1462 | version = "1.0.10" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" 1465 | 1466 | [[package]] 1467 | name = "js-sys" 1468 | version = "0.3.68" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" 1471 | dependencies = [ 1472 | "wasm-bindgen", 1473 | ] 1474 | 1475 | [[package]] 1476 | name = "jsonwebtoken" 1477 | version = "9.2.0" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "5c7ea04a7c5c055c175f189b6dc6ba036fd62306b58c66c9f6389036c503a3f4" 1480 | dependencies = [ 1481 | "base64 0.21.7", 1482 | "js-sys", 1483 | "pem", 1484 | "ring", 1485 | "serde", 1486 | "serde_json", 1487 | "simple_asn1", 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "lalrpop" 1492 | version = "0.20.2" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" 1495 | dependencies = [ 1496 | "ascii-canvas", 1497 | "bit-set", 1498 | "ena", 1499 | "itertools 0.11.0", 1500 | "lalrpop-util", 1501 | "petgraph", 1502 | "regex", 1503 | "regex-syntax 0.8.2", 1504 | "string_cache", 1505 | "term", 1506 | "tiny-keccak", 1507 | "unicode-xid", 1508 | "walkdir", 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "lalrpop-util" 1513 | version = "0.20.2" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" 1516 | dependencies = [ 1517 | "regex-automata 0.4.5", 1518 | ] 1519 | 1520 | [[package]] 1521 | name = "lazy_static" 1522 | version = "1.4.0" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1525 | dependencies = [ 1526 | "spin 0.5.2", 1527 | ] 1528 | 1529 | [[package]] 1530 | name = "libc" 1531 | version = "0.2.153" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 1534 | 1535 | [[package]] 1536 | name = "libm" 1537 | version = "0.2.8" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 1540 | 1541 | [[package]] 1542 | name = "libredox" 1543 | version = "0.0.1" 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" 1545 | checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" 1546 | dependencies = [ 1547 | "bitflags 2.4.2", 1548 | "libc", 1549 | "redox_syscall", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "lock_api" 1554 | version = "0.4.11" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 1557 | dependencies = [ 1558 | "autocfg", 1559 | "scopeguard", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "log" 1564 | version = "0.4.21" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 1567 | 1568 | [[package]] 1569 | name = "matchers" 1570 | version = "0.1.0" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 1573 | dependencies = [ 1574 | "regex-automata 0.1.10", 1575 | ] 1576 | 1577 | [[package]] 1578 | name = "matchit" 1579 | version = "0.7.3" 1580 | source = "registry+https://github.com/rust-lang/crates.io-index" 1581 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 1582 | 1583 | [[package]] 1584 | name = "md-5" 1585 | version = "0.10.6" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1588 | dependencies = [ 1589 | "cfg-if", 1590 | "digest", 1591 | ] 1592 | 1593 | [[package]] 1594 | name = "memchr" 1595 | version = "2.7.1" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" 1598 | 1599 | [[package]] 1600 | name = "memsec" 1601 | version = "0.6.3" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "0fa0916b001582d253822171bd23f4a0229d32b9507fae236f5da8cad515ba7c" 1604 | 1605 | [[package]] 1606 | name = "mime" 1607 | version = "0.3.17" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1610 | 1611 | [[package]] 1612 | name = "mime_guess" 1613 | version = "2.0.4" 1614 | source = "registry+https://github.com/rust-lang/crates.io-index" 1615 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 1616 | dependencies = [ 1617 | "mime", 1618 | "unicase", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "miniz_oxide" 1623 | version = "0.7.2" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 1626 | dependencies = [ 1627 | "adler", 1628 | ] 1629 | 1630 | [[package]] 1631 | name = "mio" 1632 | version = "0.8.11" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 1635 | dependencies = [ 1636 | "libc", 1637 | "wasi", 1638 | "windows-sys 0.48.0", 1639 | ] 1640 | 1641 | [[package]] 1642 | name = "new_debug_unreachable" 1643 | version = "1.0.4" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 1646 | 1647 | [[package]] 1648 | name = "nu-ansi-term" 1649 | version = "0.46.0" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1652 | dependencies = [ 1653 | "overload", 1654 | "winapi", 1655 | ] 1656 | 1657 | [[package]] 1658 | name = "num-bigint" 1659 | version = "0.4.4" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" 1662 | dependencies = [ 1663 | "autocfg", 1664 | "num-integer", 1665 | "num-traits", 1666 | ] 1667 | 1668 | [[package]] 1669 | name = "num-bigint-dig" 1670 | version = "0.8.4" 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" 1672 | checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 1673 | dependencies = [ 1674 | "byteorder", 1675 | "lazy_static", 1676 | "libm", 1677 | "num-integer", 1678 | "num-iter", 1679 | "num-traits", 1680 | "rand", 1681 | "smallvec", 1682 | "zeroize", 1683 | ] 1684 | 1685 | [[package]] 1686 | name = "num-conv" 1687 | version = "0.1.0" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1690 | 1691 | [[package]] 1692 | name = "num-integer" 1693 | version = "0.1.46" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1696 | dependencies = [ 1697 | "num-traits", 1698 | ] 1699 | 1700 | [[package]] 1701 | name = "num-iter" 1702 | version = "0.1.44" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" 1705 | dependencies = [ 1706 | "autocfg", 1707 | "num-integer", 1708 | "num-traits", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "num-traits" 1713 | version = "0.2.18" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 1716 | dependencies = [ 1717 | "autocfg", 1718 | "libm", 1719 | ] 1720 | 1721 | [[package]] 1722 | name = "num_cpus" 1723 | version = "1.16.0" 1724 | source = "registry+https://github.com/rust-lang/crates.io-index" 1725 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 1726 | dependencies = [ 1727 | "hermit-abi 0.3.9", 1728 | "libc", 1729 | ] 1730 | 1731 | [[package]] 1732 | name = "oauth2" 1733 | version = "4.4.2" 1734 | source = "registry+https://github.com/rust-lang/crates.io-index" 1735 | checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" 1736 | dependencies = [ 1737 | "base64 0.13.1", 1738 | "chrono", 1739 | "getrandom", 1740 | "http 0.2.11", 1741 | "rand", 1742 | "reqwest", 1743 | "serde", 1744 | "serde_json", 1745 | "serde_path_to_error", 1746 | "sha2", 1747 | "thiserror", 1748 | "url", 1749 | ] 1750 | 1751 | [[package]] 1752 | name = "object" 1753 | version = "0.32.2" 1754 | source = "registry+https://github.com/rust-lang/crates.io-index" 1755 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 1756 | dependencies = [ 1757 | "memchr", 1758 | ] 1759 | 1760 | [[package]] 1761 | name = "octocrab" 1762 | version = "0.34.1" 1763 | source = "registry+https://github.com/rust-lang/crates.io-index" 1764 | checksum = "9fc61913d67d5ed8e04b55c611ec7450ab811f83aa424f3bd00782c6b0296ae7" 1765 | dependencies = [ 1766 | "arc-swap", 1767 | "async-trait", 1768 | "base64 0.21.7", 1769 | "bytes", 1770 | "cfg-if", 1771 | "chrono", 1772 | "either", 1773 | "futures", 1774 | "futures-util", 1775 | "http 1.0.0", 1776 | "http-body 1.0.0", 1777 | "http-body-util", 1778 | "hyper 1.2.0", 1779 | "hyper-rustls 0.26.0", 1780 | "hyper-timeout 0.5.1", 1781 | "hyper-util", 1782 | "jsonwebtoken", 1783 | "once_cell", 1784 | "percent-encoding", 1785 | "pin-project", 1786 | "secrecy", 1787 | "serde", 1788 | "serde_json", 1789 | "serde_path_to_error", 1790 | "serde_urlencoded", 1791 | "snafu", 1792 | "tokio", 1793 | "tower", 1794 | "tower-http", 1795 | "tracing", 1796 | "url", 1797 | ] 1798 | 1799 | [[package]] 1800 | name = "once_cell" 1801 | version = "1.19.0" 1802 | source = "registry+https://github.com/rust-lang/crates.io-index" 1803 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1804 | 1805 | [[package]] 1806 | name = "opaque-debug" 1807 | version = "0.3.1" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1810 | 1811 | [[package]] 1812 | name = "openssl-probe" 1813 | version = "0.1.5" 1814 | source = "registry+https://github.com/rust-lang/crates.io-index" 1815 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1816 | 1817 | [[package]] 1818 | name = "opentelemetry" 1819 | version = "0.21.0" 1820 | source = "registry+https://github.com/rust-lang/crates.io-index" 1821 | checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" 1822 | dependencies = [ 1823 | "futures-core", 1824 | "futures-sink", 1825 | "indexmap 2.2.5", 1826 | "js-sys", 1827 | "once_cell", 1828 | "pin-project-lite", 1829 | "thiserror", 1830 | "urlencoding", 1831 | ] 1832 | 1833 | [[package]] 1834 | name = "opentelemetry-http" 1835 | version = "0.10.0" 1836 | source = "registry+https://github.com/rust-lang/crates.io-index" 1837 | checksum = "7f51189ce8be654f9b5f7e70e49967ed894e84a06fc35c6c042e64ac1fc5399e" 1838 | dependencies = [ 1839 | "async-trait", 1840 | "bytes", 1841 | "http 0.2.11", 1842 | "opentelemetry", 1843 | "reqwest", 1844 | ] 1845 | 1846 | [[package]] 1847 | name = "opentelemetry-otlp" 1848 | version = "0.14.0" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "f24cda83b20ed2433c68241f918d0f6fdec8b1d43b7a9590ab4420c5095ca930" 1851 | dependencies = [ 1852 | "async-trait", 1853 | "futures-core", 1854 | "http 0.2.11", 1855 | "opentelemetry", 1856 | "opentelemetry-http", 1857 | "opentelemetry-proto", 1858 | "opentelemetry-semantic-conventions", 1859 | "opentelemetry_sdk", 1860 | "prost", 1861 | "reqwest", 1862 | "thiserror", 1863 | "tokio", 1864 | "tonic", 1865 | ] 1866 | 1867 | [[package]] 1868 | name = "opentelemetry-proto" 1869 | version = "0.4.0" 1870 | source = "registry+https://github.com/rust-lang/crates.io-index" 1871 | checksum = "a2e155ce5cc812ea3d1dffbd1539aed653de4bf4882d60e6e04dcf0901d674e1" 1872 | dependencies = [ 1873 | "opentelemetry", 1874 | "opentelemetry_sdk", 1875 | "prost", 1876 | "tonic", 1877 | ] 1878 | 1879 | [[package]] 1880 | name = "opentelemetry-semantic-conventions" 1881 | version = "0.13.0" 1882 | source = "registry+https://github.com/rust-lang/crates.io-index" 1883 | checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84" 1884 | dependencies = [ 1885 | "opentelemetry", 1886 | ] 1887 | 1888 | [[package]] 1889 | name = "opentelemetry_sdk" 1890 | version = "0.21.2" 1891 | source = "registry+https://github.com/rust-lang/crates.io-index" 1892 | checksum = "2f16aec8a98a457a52664d69e0091bac3a0abd18ead9b641cb00202ba4e0efe4" 1893 | dependencies = [ 1894 | "async-trait", 1895 | "crossbeam-channel", 1896 | "futures-channel", 1897 | "futures-executor", 1898 | "futures-util", 1899 | "glob", 1900 | "once_cell", 1901 | "opentelemetry", 1902 | "ordered-float", 1903 | "percent-encoding", 1904 | "rand", 1905 | "thiserror", 1906 | "tokio", 1907 | "tokio-stream", 1908 | ] 1909 | 1910 | [[package]] 1911 | name = "ordered-float" 1912 | version = "4.2.0" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" 1915 | dependencies = [ 1916 | "num-traits", 1917 | ] 1918 | 1919 | [[package]] 1920 | name = "orion" 1921 | version = "0.17.6" 1922 | source = "registry+https://github.com/rust-lang/crates.io-index" 1923 | checksum = "7abdb10181903c8c4b016ba45d6d6d5af1a1e2a461aa4763a83b87f5df4695e5" 1924 | dependencies = [ 1925 | "ct-codecs", 1926 | "fiat-crypto", 1927 | "getrandom", 1928 | "subtle", 1929 | "zeroize", 1930 | ] 1931 | 1932 | [[package]] 1933 | name = "os_str_bytes" 1934 | version = "6.6.1" 1935 | source = "registry+https://github.com/rust-lang/crates.io-index" 1936 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 1937 | 1938 | [[package]] 1939 | name = "overload" 1940 | version = "0.1.1" 1941 | source = "registry+https://github.com/rust-lang/crates.io-index" 1942 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1943 | 1944 | [[package]] 1945 | name = "p256" 1946 | version = "0.13.2" 1947 | source = "registry+https://github.com/rust-lang/crates.io-index" 1948 | checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 1949 | dependencies = [ 1950 | "ecdsa", 1951 | "elliptic-curve", 1952 | "primeorder", 1953 | "sha2", 1954 | ] 1955 | 1956 | [[package]] 1957 | name = "parking_lot" 1958 | version = "0.12.1" 1959 | source = "registry+https://github.com/rust-lang/crates.io-index" 1960 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1961 | dependencies = [ 1962 | "lock_api", 1963 | "parking_lot_core", 1964 | ] 1965 | 1966 | [[package]] 1967 | name = "parking_lot_core" 1968 | version = "0.9.9" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" 1971 | dependencies = [ 1972 | "cfg-if", 1973 | "libc", 1974 | "redox_syscall", 1975 | "smallvec", 1976 | "windows-targets 0.48.5", 1977 | ] 1978 | 1979 | [[package]] 1980 | name = "pem" 1981 | version = "3.0.3" 1982 | source = "registry+https://github.com/rust-lang/crates.io-index" 1983 | checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" 1984 | dependencies = [ 1985 | "base64 0.21.7", 1986 | "serde", 1987 | ] 1988 | 1989 | [[package]] 1990 | name = "pem-rfc7468" 1991 | version = "0.7.0" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1994 | dependencies = [ 1995 | "base64ct", 1996 | ] 1997 | 1998 | [[package]] 1999 | name = "percent-encoding" 2000 | version = "2.3.1" 2001 | source = "registry+https://github.com/rust-lang/crates.io-index" 2002 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 2003 | 2004 | [[package]] 2005 | name = "petgraph" 2006 | version = "0.6.4" 2007 | source = "registry+https://github.com/rust-lang/crates.io-index" 2008 | checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" 2009 | dependencies = [ 2010 | "fixedbitset", 2011 | "indexmap 2.2.5", 2012 | ] 2013 | 2014 | [[package]] 2015 | name = "phf_shared" 2016 | version = "0.10.0" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 2019 | dependencies = [ 2020 | "siphasher", 2021 | ] 2022 | 2023 | [[package]] 2024 | name = "pin-project" 2025 | version = "1.1.4" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" 2028 | dependencies = [ 2029 | "pin-project-internal", 2030 | ] 2031 | 2032 | [[package]] 2033 | name = "pin-project-internal" 2034 | version = "1.1.4" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" 2037 | dependencies = [ 2038 | "proc-macro2", 2039 | "quote", 2040 | "syn 2.0.52", 2041 | ] 2042 | 2043 | [[package]] 2044 | name = "pin-project-lite" 2045 | version = "0.2.13" 2046 | source = "registry+https://github.com/rust-lang/crates.io-index" 2047 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 2048 | 2049 | [[package]] 2050 | name = "pin-utils" 2051 | version = "0.1.0" 2052 | source = "registry+https://github.com/rust-lang/crates.io-index" 2053 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 2054 | 2055 | [[package]] 2056 | name = "pkcs1" 2057 | version = "0.7.5" 2058 | source = "registry+https://github.com/rust-lang/crates.io-index" 2059 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 2060 | dependencies = [ 2061 | "der", 2062 | "pkcs8", 2063 | "spki", 2064 | ] 2065 | 2066 | [[package]] 2067 | name = "pkcs8" 2068 | version = "0.10.2" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 2071 | dependencies = [ 2072 | "der", 2073 | "spki", 2074 | ] 2075 | 2076 | [[package]] 2077 | name = "pkg-config" 2078 | version = "0.3.30" 2079 | source = "registry+https://github.com/rust-lang/crates.io-index" 2080 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 2081 | 2082 | [[package]] 2083 | name = "platforms" 2084 | version = "3.3.0" 2085 | source = "registry+https://github.com/rust-lang/crates.io-index" 2086 | checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" 2087 | 2088 | [[package]] 2089 | name = "polyval" 2090 | version = "0.6.1" 2091 | source = "registry+https://github.com/rust-lang/crates.io-index" 2092 | checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" 2093 | dependencies = [ 2094 | "cfg-if", 2095 | "cpufeatures", 2096 | "opaque-debug", 2097 | "universal-hash", 2098 | ] 2099 | 2100 | [[package]] 2101 | name = "powerfmt" 2102 | version = "0.2.0" 2103 | source = "registry+https://github.com/rust-lang/crates.io-index" 2104 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2105 | 2106 | [[package]] 2107 | name = "ppv-lite86" 2108 | version = "0.2.17" 2109 | source = "registry+https://github.com/rust-lang/crates.io-index" 2110 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 2111 | 2112 | [[package]] 2113 | name = "precomputed-hash" 2114 | version = "0.1.1" 2115 | source = "registry+https://github.com/rust-lang/crates.io-index" 2116 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 2117 | 2118 | [[package]] 2119 | name = "primeorder" 2120 | version = "0.13.6" 2121 | source = "registry+https://github.com/rust-lang/crates.io-index" 2122 | checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 2123 | dependencies = [ 2124 | "elliptic-curve", 2125 | ] 2126 | 2127 | [[package]] 2128 | name = "proc-macro-error" 2129 | version = "1.0.4" 2130 | source = "registry+https://github.com/rust-lang/crates.io-index" 2131 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 2132 | dependencies = [ 2133 | "proc-macro-error-attr", 2134 | "proc-macro2", 2135 | "quote", 2136 | "syn 1.0.109", 2137 | "version_check", 2138 | ] 2139 | 2140 | [[package]] 2141 | name = "proc-macro-error-attr" 2142 | version = "1.0.4" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 2145 | dependencies = [ 2146 | "proc-macro2", 2147 | "quote", 2148 | "version_check", 2149 | ] 2150 | 2151 | [[package]] 2152 | name = "proc-macro2" 2153 | version = "1.0.78" 2154 | source = "registry+https://github.com/rust-lang/crates.io-index" 2155 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 2156 | dependencies = [ 2157 | "unicode-ident", 2158 | ] 2159 | 2160 | [[package]] 2161 | name = "prost" 2162 | version = "0.11.9" 2163 | source = "registry+https://github.com/rust-lang/crates.io-index" 2164 | checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" 2165 | dependencies = [ 2166 | "bytes", 2167 | "prost-derive", 2168 | ] 2169 | 2170 | [[package]] 2171 | name = "prost-derive" 2172 | version = "0.11.9" 2173 | source = "registry+https://github.com/rust-lang/crates.io-index" 2174 | checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" 2175 | dependencies = [ 2176 | "anyhow", 2177 | "itertools 0.10.5", 2178 | "proc-macro2", 2179 | "quote", 2180 | "syn 1.0.109", 2181 | ] 2182 | 2183 | [[package]] 2184 | name = "quote" 2185 | version = "1.0.35" 2186 | source = "registry+https://github.com/rust-lang/crates.io-index" 2187 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 2188 | dependencies = [ 2189 | "proc-macro2", 2190 | ] 2191 | 2192 | [[package]] 2193 | name = "rand" 2194 | version = "0.8.5" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 2197 | dependencies = [ 2198 | "libc", 2199 | "rand_chacha", 2200 | "rand_core", 2201 | ] 2202 | 2203 | [[package]] 2204 | name = "rand_chacha" 2205 | version = "0.3.1" 2206 | source = "registry+https://github.com/rust-lang/crates.io-index" 2207 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 2208 | dependencies = [ 2209 | "ppv-lite86", 2210 | "rand_core", 2211 | ] 2212 | 2213 | [[package]] 2214 | name = "rand_core" 2215 | version = "0.6.4" 2216 | source = "registry+https://github.com/rust-lang/crates.io-index" 2217 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 2218 | dependencies = [ 2219 | "getrandom", 2220 | ] 2221 | 2222 | [[package]] 2223 | name = "redox_syscall" 2224 | version = "0.4.1" 2225 | source = "registry+https://github.com/rust-lang/crates.io-index" 2226 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 2227 | dependencies = [ 2228 | "bitflags 1.3.2", 2229 | ] 2230 | 2231 | [[package]] 2232 | name = "redox_users" 2233 | version = "0.4.4" 2234 | source = "registry+https://github.com/rust-lang/crates.io-index" 2235 | checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" 2236 | dependencies = [ 2237 | "getrandom", 2238 | "libredox", 2239 | "thiserror", 2240 | ] 2241 | 2242 | [[package]] 2243 | name = "regex" 2244 | version = "1.10.3" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" 2247 | dependencies = [ 2248 | "aho-corasick", 2249 | "memchr", 2250 | "regex-automata 0.4.5", 2251 | "regex-syntax 0.8.2", 2252 | ] 2253 | 2254 | [[package]] 2255 | name = "regex-automata" 2256 | version = "0.1.10" 2257 | source = "registry+https://github.com/rust-lang/crates.io-index" 2258 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 2259 | dependencies = [ 2260 | "regex-syntax 0.6.29", 2261 | ] 2262 | 2263 | [[package]] 2264 | name = "regex-automata" 2265 | version = "0.4.5" 2266 | source = "registry+https://github.com/rust-lang/crates.io-index" 2267 | checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" 2268 | dependencies = [ 2269 | "aho-corasick", 2270 | "memchr", 2271 | "regex-syntax 0.8.2", 2272 | ] 2273 | 2274 | [[package]] 2275 | name = "regex-syntax" 2276 | version = "0.6.29" 2277 | source = "registry+https://github.com/rust-lang/crates.io-index" 2278 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 2279 | 2280 | [[package]] 2281 | name = "regex-syntax" 2282 | version = "0.8.2" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 2285 | 2286 | [[package]] 2287 | name = "reqwest" 2288 | version = "0.11.24" 2289 | source = "registry+https://github.com/rust-lang/crates.io-index" 2290 | checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" 2291 | dependencies = [ 2292 | "base64 0.21.7", 2293 | "bytes", 2294 | "encoding_rs", 2295 | "futures-core", 2296 | "futures-util", 2297 | "h2 0.3.24", 2298 | "http 0.2.11", 2299 | "http-body 0.4.6", 2300 | "hyper 0.14.28", 2301 | "hyper-rustls 0.24.2", 2302 | "ipnet", 2303 | "js-sys", 2304 | "log", 2305 | "mime", 2306 | "once_cell", 2307 | "percent-encoding", 2308 | "pin-project-lite", 2309 | "rustls 0.21.10", 2310 | "rustls-pemfile 1.0.4", 2311 | "serde", 2312 | "serde_json", 2313 | "serde_urlencoded", 2314 | "sync_wrapper", 2315 | "system-configuration", 2316 | "tokio", 2317 | "tokio-rustls 0.24.1", 2318 | "tokio-util", 2319 | "tower-service", 2320 | "url", 2321 | "wasm-bindgen", 2322 | "wasm-bindgen-futures", 2323 | "wasm-streams", 2324 | "web-sys", 2325 | "webpki-roots", 2326 | "winreg", 2327 | ] 2328 | 2329 | [[package]] 2330 | name = "rfc6979" 2331 | version = "0.4.0" 2332 | source = "registry+https://github.com/rust-lang/crates.io-index" 2333 | checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 2334 | dependencies = [ 2335 | "hmac", 2336 | "subtle", 2337 | ] 2338 | 2339 | [[package]] 2340 | name = "ring" 2341 | version = "0.17.8" 2342 | source = "registry+https://github.com/rust-lang/crates.io-index" 2343 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 2344 | dependencies = [ 2345 | "cc", 2346 | "cfg-if", 2347 | "getrandom", 2348 | "libc", 2349 | "spin 0.9.8", 2350 | "untrusted", 2351 | "windows-sys 0.52.0", 2352 | ] 2353 | 2354 | [[package]] 2355 | name = "ripemd" 2356 | version = "0.1.3" 2357 | source = "registry+https://github.com/rust-lang/crates.io-index" 2358 | checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" 2359 | dependencies = [ 2360 | "digest", 2361 | ] 2362 | 2363 | [[package]] 2364 | name = "rsa" 2365 | version = "0.9.6" 2366 | source = "registry+https://github.com/rust-lang/crates.io-index" 2367 | checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" 2368 | dependencies = [ 2369 | "const-oid", 2370 | "digest", 2371 | "num-bigint-dig", 2372 | "num-integer", 2373 | "num-traits", 2374 | "pkcs1", 2375 | "pkcs8", 2376 | "rand_core", 2377 | "signature", 2378 | "spki", 2379 | "subtle", 2380 | "zeroize", 2381 | ] 2382 | 2383 | [[package]] 2384 | name = "rustc-demangle" 2385 | version = "0.1.23" 2386 | source = "registry+https://github.com/rust-lang/crates.io-index" 2387 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 2388 | 2389 | [[package]] 2390 | name = "rustc_version" 2391 | version = "0.4.0" 2392 | source = "registry+https://github.com/rust-lang/crates.io-index" 2393 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 2394 | dependencies = [ 2395 | "semver", 2396 | ] 2397 | 2398 | [[package]] 2399 | name = "rustls" 2400 | version = "0.21.10" 2401 | source = "registry+https://github.com/rust-lang/crates.io-index" 2402 | checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" 2403 | dependencies = [ 2404 | "log", 2405 | "ring", 2406 | "rustls-webpki 0.101.7", 2407 | "sct", 2408 | ] 2409 | 2410 | [[package]] 2411 | name = "rustls" 2412 | version = "0.22.2" 2413 | source = "registry+https://github.com/rust-lang/crates.io-index" 2414 | checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" 2415 | dependencies = [ 2416 | "log", 2417 | "ring", 2418 | "rustls-pki-types", 2419 | "rustls-webpki 0.102.2", 2420 | "subtle", 2421 | "zeroize", 2422 | ] 2423 | 2424 | [[package]] 2425 | name = "rustls-native-certs" 2426 | version = "0.7.0" 2427 | source = "registry+https://github.com/rust-lang/crates.io-index" 2428 | checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" 2429 | dependencies = [ 2430 | "openssl-probe", 2431 | "rustls-pemfile 2.1.1", 2432 | "rustls-pki-types", 2433 | "schannel", 2434 | "security-framework", 2435 | ] 2436 | 2437 | [[package]] 2438 | name = "rustls-pemfile" 2439 | version = "1.0.4" 2440 | source = "registry+https://github.com/rust-lang/crates.io-index" 2441 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 2442 | dependencies = [ 2443 | "base64 0.21.7", 2444 | ] 2445 | 2446 | [[package]] 2447 | name = "rustls-pemfile" 2448 | version = "2.1.1" 2449 | source = "registry+https://github.com/rust-lang/crates.io-index" 2450 | checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab" 2451 | dependencies = [ 2452 | "base64 0.21.7", 2453 | "rustls-pki-types", 2454 | ] 2455 | 2456 | [[package]] 2457 | name = "rustls-pki-types" 2458 | version = "1.3.1" 2459 | source = "registry+https://github.com/rust-lang/crates.io-index" 2460 | checksum = "5ede67b28608b4c60685c7d54122d4400d90f62b40caee7700e700380a390fa8" 2461 | 2462 | [[package]] 2463 | name = "rustls-webpki" 2464 | version = "0.101.7" 2465 | source = "registry+https://github.com/rust-lang/crates.io-index" 2466 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 2467 | dependencies = [ 2468 | "ring", 2469 | "untrusted", 2470 | ] 2471 | 2472 | [[package]] 2473 | name = "rustls-webpki" 2474 | version = "0.102.2" 2475 | source = "registry+https://github.com/rust-lang/crates.io-index" 2476 | checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" 2477 | dependencies = [ 2478 | "ring", 2479 | "rustls-pki-types", 2480 | "untrusted", 2481 | ] 2482 | 2483 | [[package]] 2484 | name = "rustversion" 2485 | version = "1.0.14" 2486 | source = "registry+https://github.com/rust-lang/crates.io-index" 2487 | checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" 2488 | 2489 | [[package]] 2490 | name = "ryu" 2491 | version = "1.0.17" 2492 | source = "registry+https://github.com/rust-lang/crates.io-index" 2493 | checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" 2494 | 2495 | [[package]] 2496 | name = "same-file" 2497 | version = "1.0.6" 2498 | source = "registry+https://github.com/rust-lang/crates.io-index" 2499 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 2500 | dependencies = [ 2501 | "winapi-util", 2502 | ] 2503 | 2504 | [[package]] 2505 | name = "schannel" 2506 | version = "0.1.23" 2507 | source = "registry+https://github.com/rust-lang/crates.io-index" 2508 | checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" 2509 | dependencies = [ 2510 | "windows-sys 0.52.0", 2511 | ] 2512 | 2513 | [[package]] 2514 | name = "scopeguard" 2515 | version = "1.2.0" 2516 | source = "registry+https://github.com/rust-lang/crates.io-index" 2517 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2518 | 2519 | [[package]] 2520 | name = "sct" 2521 | version = "0.7.1" 2522 | source = "registry+https://github.com/rust-lang/crates.io-index" 2523 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 2524 | dependencies = [ 2525 | "ring", 2526 | "untrusted", 2527 | ] 2528 | 2529 | [[package]] 2530 | name = "sec1" 2531 | version = "0.7.3" 2532 | source = "registry+https://github.com/rust-lang/crates.io-index" 2533 | checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 2534 | dependencies = [ 2535 | "base16ct", 2536 | "der", 2537 | "generic-array 0.14.7", 2538 | "pkcs8", 2539 | "subtle", 2540 | "zeroize", 2541 | ] 2542 | 2543 | [[package]] 2544 | name = "secrecy" 2545 | version = "0.8.0" 2546 | source = "registry+https://github.com/rust-lang/crates.io-index" 2547 | checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" 2548 | dependencies = [ 2549 | "zeroize", 2550 | ] 2551 | 2552 | [[package]] 2553 | name = "security-framework" 2554 | version = "2.9.2" 2555 | source = "registry+https://github.com/rust-lang/crates.io-index" 2556 | checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" 2557 | dependencies = [ 2558 | "bitflags 1.3.2", 2559 | "core-foundation", 2560 | "core-foundation-sys", 2561 | "libc", 2562 | "security-framework-sys", 2563 | ] 2564 | 2565 | [[package]] 2566 | name = "security-framework-sys" 2567 | version = "2.9.1" 2568 | source = "registry+https://github.com/rust-lang/crates.io-index" 2569 | checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" 2570 | dependencies = [ 2571 | "core-foundation-sys", 2572 | "libc", 2573 | ] 2574 | 2575 | [[package]] 2576 | name = "semver" 2577 | version = "1.0.22" 2578 | source = "registry+https://github.com/rust-lang/crates.io-index" 2579 | checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" 2580 | 2581 | [[package]] 2582 | name = "sequoia-openpgp" 2583 | version = "1.19.0" 2584 | source = "registry+https://github.com/rust-lang/crates.io-index" 2585 | checksum = "ebf154ce4af3d7983de8fded403f98ff9eb3ee38dffccea0472ac38aa4276df4" 2586 | dependencies = [ 2587 | "aes", 2588 | "aes-gcm", 2589 | "anyhow", 2590 | "base64 0.21.7", 2591 | "block-padding", 2592 | "blowfish", 2593 | "buffered-reader", 2594 | "camellia", 2595 | "cast5", 2596 | "cfb-mode", 2597 | "chrono", 2598 | "cipher", 2599 | "des", 2600 | "digest", 2601 | "dsa", 2602 | "dyn-clone", 2603 | "eax", 2604 | "ecb", 2605 | "ecdsa", 2606 | "ed25519", 2607 | "ed25519-dalek", 2608 | "getrandom", 2609 | "idea", 2610 | "idna", 2611 | "lalrpop", 2612 | "lalrpop-util", 2613 | "lazy_static", 2614 | "libc", 2615 | "md-5", 2616 | "memsec", 2617 | "num-bigint-dig", 2618 | "once_cell", 2619 | "p256", 2620 | "rand", 2621 | "rand_core", 2622 | "regex", 2623 | "regex-syntax 0.8.2", 2624 | "ripemd", 2625 | "rsa", 2626 | "sha1collisiondetection", 2627 | "sha2", 2628 | "thiserror", 2629 | "twofish", 2630 | "typenum", 2631 | "x25519-dalek", 2632 | "xxhash-rust", 2633 | ] 2634 | 2635 | [[package]] 2636 | name = "serde" 2637 | version = "1.0.197" 2638 | source = "registry+https://github.com/rust-lang/crates.io-index" 2639 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 2640 | dependencies = [ 2641 | "serde_derive", 2642 | ] 2643 | 2644 | [[package]] 2645 | name = "serde_derive" 2646 | version = "1.0.197" 2647 | source = "registry+https://github.com/rust-lang/crates.io-index" 2648 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 2649 | dependencies = [ 2650 | "proc-macro2", 2651 | "quote", 2652 | "syn 2.0.52", 2653 | ] 2654 | 2655 | [[package]] 2656 | name = "serde_json" 2657 | version = "1.0.114" 2658 | source = "registry+https://github.com/rust-lang/crates.io-index" 2659 | checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" 2660 | dependencies = [ 2661 | "itoa", 2662 | "ryu", 2663 | "serde", 2664 | ] 2665 | 2666 | [[package]] 2667 | name = "serde_path_to_error" 2668 | version = "0.1.15" 2669 | source = "registry+https://github.com/rust-lang/crates.io-index" 2670 | checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" 2671 | dependencies = [ 2672 | "itoa", 2673 | "serde", 2674 | ] 2675 | 2676 | [[package]] 2677 | name = "serde_spanned" 2678 | version = "0.6.5" 2679 | source = "registry+https://github.com/rust-lang/crates.io-index" 2680 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 2681 | dependencies = [ 2682 | "serde", 2683 | ] 2684 | 2685 | [[package]] 2686 | name = "serde_urlencoded" 2687 | version = "0.7.1" 2688 | source = "registry+https://github.com/rust-lang/crates.io-index" 2689 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2690 | dependencies = [ 2691 | "form_urlencoded", 2692 | "itoa", 2693 | "ryu", 2694 | "serde", 2695 | ] 2696 | 2697 | [[package]] 2698 | name = "sha1collisiondetection" 2699 | version = "0.3.3" 2700 | source = "registry+https://github.com/rust-lang/crates.io-index" 2701 | checksum = "f1d5c4be690002e8a5d7638b0b7323f03c268c7a919bd8af69ce963a4dc83220" 2702 | dependencies = [ 2703 | "const-oid", 2704 | "digest", 2705 | "generic-array 1.0.0", 2706 | ] 2707 | 2708 | [[package]] 2709 | name = "sha2" 2710 | version = "0.10.8" 2711 | source = "registry+https://github.com/rust-lang/crates.io-index" 2712 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 2713 | dependencies = [ 2714 | "cfg-if", 2715 | "cpufeatures", 2716 | "digest", 2717 | ] 2718 | 2719 | [[package]] 2720 | name = "sharded-slab" 2721 | version = "0.1.7" 2722 | source = "registry+https://github.com/rust-lang/crates.io-index" 2723 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 2724 | dependencies = [ 2725 | "lazy_static", 2726 | ] 2727 | 2728 | [[package]] 2729 | name = "signal-hook-registry" 2730 | version = "1.4.1" 2731 | source = "registry+https://github.com/rust-lang/crates.io-index" 2732 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 2733 | dependencies = [ 2734 | "libc", 2735 | ] 2736 | 2737 | [[package]] 2738 | name = "signature" 2739 | version = "2.2.0" 2740 | source = "registry+https://github.com/rust-lang/crates.io-index" 2741 | checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 2742 | dependencies = [ 2743 | "digest", 2744 | "rand_core", 2745 | ] 2746 | 2747 | [[package]] 2748 | name = "simple_asn1" 2749 | version = "0.6.2" 2750 | source = "registry+https://github.com/rust-lang/crates.io-index" 2751 | checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" 2752 | dependencies = [ 2753 | "num-bigint", 2754 | "num-traits", 2755 | "thiserror", 2756 | "time", 2757 | ] 2758 | 2759 | [[package]] 2760 | name = "siphasher" 2761 | version = "0.3.11" 2762 | source = "registry+https://github.com/rust-lang/crates.io-index" 2763 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 2764 | 2765 | [[package]] 2766 | name = "slab" 2767 | version = "0.4.9" 2768 | source = "registry+https://github.com/rust-lang/crates.io-index" 2769 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 2770 | dependencies = [ 2771 | "autocfg", 2772 | ] 2773 | 2774 | [[package]] 2775 | name = "smallvec" 2776 | version = "1.13.1" 2777 | source = "registry+https://github.com/rust-lang/crates.io-index" 2778 | checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" 2779 | 2780 | [[package]] 2781 | name = "snafu" 2782 | version = "0.7.5" 2783 | source = "registry+https://github.com/rust-lang/crates.io-index" 2784 | checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" 2785 | dependencies = [ 2786 | "backtrace", 2787 | "doc-comment", 2788 | "snafu-derive", 2789 | ] 2790 | 2791 | [[package]] 2792 | name = "snafu-derive" 2793 | version = "0.7.5" 2794 | source = "registry+https://github.com/rust-lang/crates.io-index" 2795 | checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" 2796 | dependencies = [ 2797 | "heck", 2798 | "proc-macro2", 2799 | "quote", 2800 | "syn 1.0.109", 2801 | ] 2802 | 2803 | [[package]] 2804 | name = "socket2" 2805 | version = "0.5.6" 2806 | source = "registry+https://github.com/rust-lang/crates.io-index" 2807 | checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" 2808 | dependencies = [ 2809 | "libc", 2810 | "windows-sys 0.52.0", 2811 | ] 2812 | 2813 | [[package]] 2814 | name = "spin" 2815 | version = "0.5.2" 2816 | source = "registry+https://github.com/rust-lang/crates.io-index" 2817 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 2818 | 2819 | [[package]] 2820 | name = "spin" 2821 | version = "0.9.8" 2822 | source = "registry+https://github.com/rust-lang/crates.io-index" 2823 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 2824 | 2825 | [[package]] 2826 | name = "spki" 2827 | version = "0.7.3" 2828 | source = "registry+https://github.com/rust-lang/crates.io-index" 2829 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 2830 | dependencies = [ 2831 | "base64ct", 2832 | "der", 2833 | ] 2834 | 2835 | [[package]] 2836 | name = "string_cache" 2837 | version = "0.8.7" 2838 | source = "registry+https://github.com/rust-lang/crates.io-index" 2839 | checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" 2840 | dependencies = [ 2841 | "new_debug_unreachable", 2842 | "once_cell", 2843 | "parking_lot", 2844 | "phf_shared", 2845 | "precomputed-hash", 2846 | ] 2847 | 2848 | [[package]] 2849 | name = "strsim" 2850 | version = "0.10.0" 2851 | source = "registry+https://github.com/rust-lang/crates.io-index" 2852 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 2853 | 2854 | [[package]] 2855 | name = "subtle" 2856 | version = "2.5.0" 2857 | source = "registry+https://github.com/rust-lang/crates.io-index" 2858 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 2859 | 2860 | [[package]] 2861 | name = "syn" 2862 | version = "1.0.109" 2863 | source = "registry+https://github.com/rust-lang/crates.io-index" 2864 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 2865 | dependencies = [ 2866 | "proc-macro2", 2867 | "quote", 2868 | "unicode-ident", 2869 | ] 2870 | 2871 | [[package]] 2872 | name = "syn" 2873 | version = "2.0.52" 2874 | source = "registry+https://github.com/rust-lang/crates.io-index" 2875 | checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" 2876 | dependencies = [ 2877 | "proc-macro2", 2878 | "quote", 2879 | "unicode-ident", 2880 | ] 2881 | 2882 | [[package]] 2883 | name = "sync_wrapper" 2884 | version = "0.1.2" 2885 | source = "registry+https://github.com/rust-lang/crates.io-index" 2886 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 2887 | 2888 | [[package]] 2889 | name = "system-configuration" 2890 | version = "0.5.1" 2891 | source = "registry+https://github.com/rust-lang/crates.io-index" 2892 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 2893 | dependencies = [ 2894 | "bitflags 1.3.2", 2895 | "core-foundation", 2896 | "system-configuration-sys", 2897 | ] 2898 | 2899 | [[package]] 2900 | name = "system-configuration-sys" 2901 | version = "0.5.0" 2902 | source = "registry+https://github.com/rust-lang/crates.io-index" 2903 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 2904 | dependencies = [ 2905 | "core-foundation-sys", 2906 | "libc", 2907 | ] 2908 | 2909 | [[package]] 2910 | name = "term" 2911 | version = "0.7.0" 2912 | source = "registry+https://github.com/rust-lang/crates.io-index" 2913 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 2914 | dependencies = [ 2915 | "dirs-next", 2916 | "rustversion", 2917 | "winapi", 2918 | ] 2919 | 2920 | [[package]] 2921 | name = "termcolor" 2922 | version = "1.4.1" 2923 | source = "registry+https://github.com/rust-lang/crates.io-index" 2924 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 2925 | dependencies = [ 2926 | "winapi-util", 2927 | ] 2928 | 2929 | [[package]] 2930 | name = "textwrap" 2931 | version = "0.16.1" 2932 | source = "registry+https://github.com/rust-lang/crates.io-index" 2933 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 2934 | 2935 | [[package]] 2936 | name = "tfreg" 2937 | version = "0.2.0" 2938 | dependencies = [ 2939 | "anyhow", 2940 | "async-trait", 2941 | "axum 0.7.4", 2942 | "base64 0.21.7", 2943 | "clap", 2944 | "futures", 2945 | "futures-util", 2946 | "hex", 2947 | "http-body 1.0.0", 2948 | "http-body-util", 2949 | "oauth2", 2950 | "octocrab", 2951 | "opentelemetry", 2952 | "opentelemetry-otlp", 2953 | "opentelemetry_sdk", 2954 | "orion", 2955 | "reqwest", 2956 | "sequoia-openpgp", 2957 | "serde", 2958 | "serde_json", 2959 | "serde_urlencoded", 2960 | "thiserror", 2961 | "tokio", 2962 | "tokio-util", 2963 | "toml", 2964 | "tower", 2965 | "tower-http", 2966 | "tracing", 2967 | "tracing-opentelemetry", 2968 | "tracing-subscriber", 2969 | "url", 2970 | ] 2971 | 2972 | [[package]] 2973 | name = "thiserror" 2974 | version = "1.0.57" 2975 | source = "registry+https://github.com/rust-lang/crates.io-index" 2976 | checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" 2977 | dependencies = [ 2978 | "thiserror-impl", 2979 | ] 2980 | 2981 | [[package]] 2982 | name = "thiserror-impl" 2983 | version = "1.0.57" 2984 | source = "registry+https://github.com/rust-lang/crates.io-index" 2985 | checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" 2986 | dependencies = [ 2987 | "proc-macro2", 2988 | "quote", 2989 | "syn 2.0.52", 2990 | ] 2991 | 2992 | [[package]] 2993 | name = "thread_local" 2994 | version = "1.1.8" 2995 | source = "registry+https://github.com/rust-lang/crates.io-index" 2996 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 2997 | dependencies = [ 2998 | "cfg-if", 2999 | "once_cell", 3000 | ] 3001 | 3002 | [[package]] 3003 | name = "time" 3004 | version = "0.3.34" 3005 | source = "registry+https://github.com/rust-lang/crates.io-index" 3006 | checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" 3007 | dependencies = [ 3008 | "deranged", 3009 | "itoa", 3010 | "num-conv", 3011 | "powerfmt", 3012 | "serde", 3013 | "time-core", 3014 | "time-macros", 3015 | ] 3016 | 3017 | [[package]] 3018 | name = "time-core" 3019 | version = "0.1.2" 3020 | source = "registry+https://github.com/rust-lang/crates.io-index" 3021 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 3022 | 3023 | [[package]] 3024 | name = "time-macros" 3025 | version = "0.2.17" 3026 | source = "registry+https://github.com/rust-lang/crates.io-index" 3027 | checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" 3028 | dependencies = [ 3029 | "num-conv", 3030 | "time-core", 3031 | ] 3032 | 3033 | [[package]] 3034 | name = "tiny-keccak" 3035 | version = "2.0.2" 3036 | source = "registry+https://github.com/rust-lang/crates.io-index" 3037 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 3038 | dependencies = [ 3039 | "crunchy", 3040 | ] 3041 | 3042 | [[package]] 3043 | name = "tinyvec" 3044 | version = "1.6.0" 3045 | source = "registry+https://github.com/rust-lang/crates.io-index" 3046 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 3047 | dependencies = [ 3048 | "tinyvec_macros", 3049 | ] 3050 | 3051 | [[package]] 3052 | name = "tinyvec_macros" 3053 | version = "0.1.1" 3054 | source = "registry+https://github.com/rust-lang/crates.io-index" 3055 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 3056 | 3057 | [[package]] 3058 | name = "tokio" 3059 | version = "1.36.0" 3060 | source = "registry+https://github.com/rust-lang/crates.io-index" 3061 | checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" 3062 | dependencies = [ 3063 | "backtrace", 3064 | "bytes", 3065 | "libc", 3066 | "mio", 3067 | "num_cpus", 3068 | "parking_lot", 3069 | "pin-project-lite", 3070 | "signal-hook-registry", 3071 | "socket2", 3072 | "tokio-macros", 3073 | "windows-sys 0.48.0", 3074 | ] 3075 | 3076 | [[package]] 3077 | name = "tokio-io-timeout" 3078 | version = "1.2.0" 3079 | source = "registry+https://github.com/rust-lang/crates.io-index" 3080 | checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" 3081 | dependencies = [ 3082 | "pin-project-lite", 3083 | "tokio", 3084 | ] 3085 | 3086 | [[package]] 3087 | name = "tokio-macros" 3088 | version = "2.2.0" 3089 | source = "registry+https://github.com/rust-lang/crates.io-index" 3090 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 3091 | dependencies = [ 3092 | "proc-macro2", 3093 | "quote", 3094 | "syn 2.0.52", 3095 | ] 3096 | 3097 | [[package]] 3098 | name = "tokio-rustls" 3099 | version = "0.24.1" 3100 | source = "registry+https://github.com/rust-lang/crates.io-index" 3101 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 3102 | dependencies = [ 3103 | "rustls 0.21.10", 3104 | "tokio", 3105 | ] 3106 | 3107 | [[package]] 3108 | name = "tokio-rustls" 3109 | version = "0.25.0" 3110 | source = "registry+https://github.com/rust-lang/crates.io-index" 3111 | checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" 3112 | dependencies = [ 3113 | "rustls 0.22.2", 3114 | "rustls-pki-types", 3115 | "tokio", 3116 | ] 3117 | 3118 | [[package]] 3119 | name = "tokio-stream" 3120 | version = "0.1.14" 3121 | source = "registry+https://github.com/rust-lang/crates.io-index" 3122 | checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" 3123 | dependencies = [ 3124 | "futures-core", 3125 | "pin-project-lite", 3126 | "tokio", 3127 | ] 3128 | 3129 | [[package]] 3130 | name = "tokio-util" 3131 | version = "0.7.10" 3132 | source = "registry+https://github.com/rust-lang/crates.io-index" 3133 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 3134 | dependencies = [ 3135 | "bytes", 3136 | "futures-core", 3137 | "futures-sink", 3138 | "pin-project-lite", 3139 | "tokio", 3140 | "tracing", 3141 | ] 3142 | 3143 | [[package]] 3144 | name = "toml" 3145 | version = "0.7.8" 3146 | source = "registry+https://github.com/rust-lang/crates.io-index" 3147 | checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 3148 | dependencies = [ 3149 | "serde", 3150 | "serde_spanned", 3151 | "toml_datetime", 3152 | "toml_edit", 3153 | ] 3154 | 3155 | [[package]] 3156 | name = "toml_datetime" 3157 | version = "0.6.5" 3158 | source = "registry+https://github.com/rust-lang/crates.io-index" 3159 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 3160 | dependencies = [ 3161 | "serde", 3162 | ] 3163 | 3164 | [[package]] 3165 | name = "toml_edit" 3166 | version = "0.19.15" 3167 | source = "registry+https://github.com/rust-lang/crates.io-index" 3168 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 3169 | dependencies = [ 3170 | "indexmap 2.2.5", 3171 | "serde", 3172 | "serde_spanned", 3173 | "toml_datetime", 3174 | "winnow", 3175 | ] 3176 | 3177 | [[package]] 3178 | name = "tonic" 3179 | version = "0.9.2" 3180 | source = "registry+https://github.com/rust-lang/crates.io-index" 3181 | checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" 3182 | dependencies = [ 3183 | "async-trait", 3184 | "axum 0.6.20", 3185 | "base64 0.21.7", 3186 | "bytes", 3187 | "futures-core", 3188 | "futures-util", 3189 | "h2 0.3.24", 3190 | "http 0.2.11", 3191 | "http-body 0.4.6", 3192 | "hyper 0.14.28", 3193 | "hyper-timeout 0.4.1", 3194 | "percent-encoding", 3195 | "pin-project", 3196 | "prost", 3197 | "tokio", 3198 | "tokio-stream", 3199 | "tower", 3200 | "tower-layer", 3201 | "tower-service", 3202 | "tracing", 3203 | ] 3204 | 3205 | [[package]] 3206 | name = "tower" 3207 | version = "0.4.13" 3208 | source = "registry+https://github.com/rust-lang/crates.io-index" 3209 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 3210 | dependencies = [ 3211 | "futures-core", 3212 | "futures-util", 3213 | "indexmap 1.9.3", 3214 | "pin-project", 3215 | "pin-project-lite", 3216 | "rand", 3217 | "slab", 3218 | "tokio", 3219 | "tokio-util", 3220 | "tower-layer", 3221 | "tower-service", 3222 | "tracing", 3223 | ] 3224 | 3225 | [[package]] 3226 | name = "tower-http" 3227 | version = "0.5.2" 3228 | source = "registry+https://github.com/rust-lang/crates.io-index" 3229 | checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" 3230 | dependencies = [ 3231 | "async-compression", 3232 | "bitflags 2.4.2", 3233 | "bytes", 3234 | "futures-core", 3235 | "futures-util", 3236 | "http 1.0.0", 3237 | "http-body 1.0.0", 3238 | "http-body-util", 3239 | "http-range-header", 3240 | "httpdate", 3241 | "iri-string", 3242 | "mime", 3243 | "mime_guess", 3244 | "percent-encoding", 3245 | "pin-project-lite", 3246 | "tokio", 3247 | "tokio-util", 3248 | "tower", 3249 | "tower-layer", 3250 | "tower-service", 3251 | "tracing", 3252 | ] 3253 | 3254 | [[package]] 3255 | name = "tower-layer" 3256 | version = "0.3.2" 3257 | source = "registry+https://github.com/rust-lang/crates.io-index" 3258 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 3259 | 3260 | [[package]] 3261 | name = "tower-service" 3262 | version = "0.3.2" 3263 | source = "registry+https://github.com/rust-lang/crates.io-index" 3264 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 3265 | 3266 | [[package]] 3267 | name = "tracing" 3268 | version = "0.1.40" 3269 | source = "registry+https://github.com/rust-lang/crates.io-index" 3270 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 3271 | dependencies = [ 3272 | "log", 3273 | "pin-project-lite", 3274 | "tracing-attributes", 3275 | "tracing-core", 3276 | ] 3277 | 3278 | [[package]] 3279 | name = "tracing-attributes" 3280 | version = "0.1.27" 3281 | source = "registry+https://github.com/rust-lang/crates.io-index" 3282 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 3283 | dependencies = [ 3284 | "proc-macro2", 3285 | "quote", 3286 | "syn 2.0.52", 3287 | ] 3288 | 3289 | [[package]] 3290 | name = "tracing-core" 3291 | version = "0.1.32" 3292 | source = "registry+https://github.com/rust-lang/crates.io-index" 3293 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 3294 | dependencies = [ 3295 | "once_cell", 3296 | "valuable", 3297 | ] 3298 | 3299 | [[package]] 3300 | name = "tracing-log" 3301 | version = "0.2.0" 3302 | source = "registry+https://github.com/rust-lang/crates.io-index" 3303 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 3304 | dependencies = [ 3305 | "log", 3306 | "once_cell", 3307 | "tracing-core", 3308 | ] 3309 | 3310 | [[package]] 3311 | name = "tracing-opentelemetry" 3312 | version = "0.22.0" 3313 | source = "registry+https://github.com/rust-lang/crates.io-index" 3314 | checksum = "c67ac25c5407e7b961fafc6f7e9aa5958fd297aada2d20fa2ae1737357e55596" 3315 | dependencies = [ 3316 | "js-sys", 3317 | "once_cell", 3318 | "opentelemetry", 3319 | "opentelemetry_sdk", 3320 | "smallvec", 3321 | "tracing", 3322 | "tracing-core", 3323 | "tracing-log", 3324 | "tracing-subscriber", 3325 | "web-time", 3326 | ] 3327 | 3328 | [[package]] 3329 | name = "tracing-serde" 3330 | version = "0.1.3" 3331 | source = "registry+https://github.com/rust-lang/crates.io-index" 3332 | checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" 3333 | dependencies = [ 3334 | "serde", 3335 | "tracing-core", 3336 | ] 3337 | 3338 | [[package]] 3339 | name = "tracing-subscriber" 3340 | version = "0.3.18" 3341 | source = "registry+https://github.com/rust-lang/crates.io-index" 3342 | checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 3343 | dependencies = [ 3344 | "matchers", 3345 | "nu-ansi-term", 3346 | "once_cell", 3347 | "regex", 3348 | "serde", 3349 | "serde_json", 3350 | "sharded-slab", 3351 | "smallvec", 3352 | "thread_local", 3353 | "tracing", 3354 | "tracing-core", 3355 | "tracing-log", 3356 | "tracing-serde", 3357 | ] 3358 | 3359 | [[package]] 3360 | name = "try-lock" 3361 | version = "0.2.5" 3362 | source = "registry+https://github.com/rust-lang/crates.io-index" 3363 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3364 | 3365 | [[package]] 3366 | name = "twofish" 3367 | version = "0.7.1" 3368 | source = "registry+https://github.com/rust-lang/crates.io-index" 3369 | checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" 3370 | dependencies = [ 3371 | "cipher", 3372 | ] 3373 | 3374 | [[package]] 3375 | name = "typenum" 3376 | version = "1.17.0" 3377 | source = "registry+https://github.com/rust-lang/crates.io-index" 3378 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 3379 | 3380 | [[package]] 3381 | name = "unicase" 3382 | version = "2.7.0" 3383 | source = "registry+https://github.com/rust-lang/crates.io-index" 3384 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 3385 | dependencies = [ 3386 | "version_check", 3387 | ] 3388 | 3389 | [[package]] 3390 | name = "unicode-bidi" 3391 | version = "0.3.15" 3392 | source = "registry+https://github.com/rust-lang/crates.io-index" 3393 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 3394 | 3395 | [[package]] 3396 | name = "unicode-ident" 3397 | version = "1.0.12" 3398 | source = "registry+https://github.com/rust-lang/crates.io-index" 3399 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 3400 | 3401 | [[package]] 3402 | name = "unicode-normalization" 3403 | version = "0.1.23" 3404 | source = "registry+https://github.com/rust-lang/crates.io-index" 3405 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 3406 | dependencies = [ 3407 | "tinyvec", 3408 | ] 3409 | 3410 | [[package]] 3411 | name = "unicode-xid" 3412 | version = "0.2.4" 3413 | source = "registry+https://github.com/rust-lang/crates.io-index" 3414 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 3415 | 3416 | [[package]] 3417 | name = "universal-hash" 3418 | version = "0.5.1" 3419 | source = "registry+https://github.com/rust-lang/crates.io-index" 3420 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 3421 | dependencies = [ 3422 | "crypto-common", 3423 | "subtle", 3424 | ] 3425 | 3426 | [[package]] 3427 | name = "untrusted" 3428 | version = "0.9.0" 3429 | source = "registry+https://github.com/rust-lang/crates.io-index" 3430 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 3431 | 3432 | [[package]] 3433 | name = "url" 3434 | version = "2.5.0" 3435 | source = "registry+https://github.com/rust-lang/crates.io-index" 3436 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 3437 | dependencies = [ 3438 | "form_urlencoded", 3439 | "idna", 3440 | "percent-encoding", 3441 | "serde", 3442 | ] 3443 | 3444 | [[package]] 3445 | name = "urlencoding" 3446 | version = "2.1.3" 3447 | source = "registry+https://github.com/rust-lang/crates.io-index" 3448 | checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 3449 | 3450 | [[package]] 3451 | name = "valuable" 3452 | version = "0.1.0" 3453 | source = "registry+https://github.com/rust-lang/crates.io-index" 3454 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 3455 | 3456 | [[package]] 3457 | name = "version_check" 3458 | version = "0.9.4" 3459 | source = "registry+https://github.com/rust-lang/crates.io-index" 3460 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 3461 | 3462 | [[package]] 3463 | name = "walkdir" 3464 | version = "2.5.0" 3465 | source = "registry+https://github.com/rust-lang/crates.io-index" 3466 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 3467 | dependencies = [ 3468 | "same-file", 3469 | "winapi-util", 3470 | ] 3471 | 3472 | [[package]] 3473 | name = "want" 3474 | version = "0.3.1" 3475 | source = "registry+https://github.com/rust-lang/crates.io-index" 3476 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 3477 | dependencies = [ 3478 | "try-lock", 3479 | ] 3480 | 3481 | [[package]] 3482 | name = "wasi" 3483 | version = "0.11.0+wasi-snapshot-preview1" 3484 | source = "registry+https://github.com/rust-lang/crates.io-index" 3485 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 3486 | 3487 | [[package]] 3488 | name = "wasm-bindgen" 3489 | version = "0.2.91" 3490 | source = "registry+https://github.com/rust-lang/crates.io-index" 3491 | checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" 3492 | dependencies = [ 3493 | "cfg-if", 3494 | "wasm-bindgen-macro", 3495 | ] 3496 | 3497 | [[package]] 3498 | name = "wasm-bindgen-backend" 3499 | version = "0.2.91" 3500 | source = "registry+https://github.com/rust-lang/crates.io-index" 3501 | checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" 3502 | dependencies = [ 3503 | "bumpalo", 3504 | "log", 3505 | "once_cell", 3506 | "proc-macro2", 3507 | "quote", 3508 | "syn 2.0.52", 3509 | "wasm-bindgen-shared", 3510 | ] 3511 | 3512 | [[package]] 3513 | name = "wasm-bindgen-futures" 3514 | version = "0.4.41" 3515 | source = "registry+https://github.com/rust-lang/crates.io-index" 3516 | checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" 3517 | dependencies = [ 3518 | "cfg-if", 3519 | "js-sys", 3520 | "wasm-bindgen", 3521 | "web-sys", 3522 | ] 3523 | 3524 | [[package]] 3525 | name = "wasm-bindgen-macro" 3526 | version = "0.2.91" 3527 | source = "registry+https://github.com/rust-lang/crates.io-index" 3528 | checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" 3529 | dependencies = [ 3530 | "quote", 3531 | "wasm-bindgen-macro-support", 3532 | ] 3533 | 3534 | [[package]] 3535 | name = "wasm-bindgen-macro-support" 3536 | version = "0.2.91" 3537 | source = "registry+https://github.com/rust-lang/crates.io-index" 3538 | checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" 3539 | dependencies = [ 3540 | "proc-macro2", 3541 | "quote", 3542 | "syn 2.0.52", 3543 | "wasm-bindgen-backend", 3544 | "wasm-bindgen-shared", 3545 | ] 3546 | 3547 | [[package]] 3548 | name = "wasm-bindgen-shared" 3549 | version = "0.2.91" 3550 | source = "registry+https://github.com/rust-lang/crates.io-index" 3551 | checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" 3552 | 3553 | [[package]] 3554 | name = "wasm-streams" 3555 | version = "0.4.0" 3556 | source = "registry+https://github.com/rust-lang/crates.io-index" 3557 | checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" 3558 | dependencies = [ 3559 | "futures-util", 3560 | "js-sys", 3561 | "wasm-bindgen", 3562 | "wasm-bindgen-futures", 3563 | "web-sys", 3564 | ] 3565 | 3566 | [[package]] 3567 | name = "web-sys" 3568 | version = "0.3.68" 3569 | source = "registry+https://github.com/rust-lang/crates.io-index" 3570 | checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" 3571 | dependencies = [ 3572 | "js-sys", 3573 | "wasm-bindgen", 3574 | ] 3575 | 3576 | [[package]] 3577 | name = "web-time" 3578 | version = "0.2.4" 3579 | source = "registry+https://github.com/rust-lang/crates.io-index" 3580 | checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" 3581 | dependencies = [ 3582 | "js-sys", 3583 | "wasm-bindgen", 3584 | ] 3585 | 3586 | [[package]] 3587 | name = "webpki-roots" 3588 | version = "0.25.4" 3589 | source = "registry+https://github.com/rust-lang/crates.io-index" 3590 | checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 3591 | 3592 | [[package]] 3593 | name = "winapi" 3594 | version = "0.3.9" 3595 | source = "registry+https://github.com/rust-lang/crates.io-index" 3596 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 3597 | dependencies = [ 3598 | "winapi-i686-pc-windows-gnu", 3599 | "winapi-x86_64-pc-windows-gnu", 3600 | ] 3601 | 3602 | [[package]] 3603 | name = "winapi-i686-pc-windows-gnu" 3604 | version = "0.4.0" 3605 | source = "registry+https://github.com/rust-lang/crates.io-index" 3606 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 3607 | 3608 | [[package]] 3609 | name = "winapi-util" 3610 | version = "0.1.6" 3611 | source = "registry+https://github.com/rust-lang/crates.io-index" 3612 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 3613 | dependencies = [ 3614 | "winapi", 3615 | ] 3616 | 3617 | [[package]] 3618 | name = "winapi-x86_64-pc-windows-gnu" 3619 | version = "0.4.0" 3620 | source = "registry+https://github.com/rust-lang/crates.io-index" 3621 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 3622 | 3623 | [[package]] 3624 | name = "windows-core" 3625 | version = "0.52.0" 3626 | source = "registry+https://github.com/rust-lang/crates.io-index" 3627 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 3628 | dependencies = [ 3629 | "windows-targets 0.52.4", 3630 | ] 3631 | 3632 | [[package]] 3633 | name = "windows-sys" 3634 | version = "0.48.0" 3635 | source = "registry+https://github.com/rust-lang/crates.io-index" 3636 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 3637 | dependencies = [ 3638 | "windows-targets 0.48.5", 3639 | ] 3640 | 3641 | [[package]] 3642 | name = "windows-sys" 3643 | version = "0.52.0" 3644 | source = "registry+https://github.com/rust-lang/crates.io-index" 3645 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3646 | dependencies = [ 3647 | "windows-targets 0.52.4", 3648 | ] 3649 | 3650 | [[package]] 3651 | name = "windows-targets" 3652 | version = "0.48.5" 3653 | source = "registry+https://github.com/rust-lang/crates.io-index" 3654 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 3655 | dependencies = [ 3656 | "windows_aarch64_gnullvm 0.48.5", 3657 | "windows_aarch64_msvc 0.48.5", 3658 | "windows_i686_gnu 0.48.5", 3659 | "windows_i686_msvc 0.48.5", 3660 | "windows_x86_64_gnu 0.48.5", 3661 | "windows_x86_64_gnullvm 0.48.5", 3662 | "windows_x86_64_msvc 0.48.5", 3663 | ] 3664 | 3665 | [[package]] 3666 | name = "windows-targets" 3667 | version = "0.52.4" 3668 | source = "registry+https://github.com/rust-lang/crates.io-index" 3669 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 3670 | dependencies = [ 3671 | "windows_aarch64_gnullvm 0.52.4", 3672 | "windows_aarch64_msvc 0.52.4", 3673 | "windows_i686_gnu 0.52.4", 3674 | "windows_i686_msvc 0.52.4", 3675 | "windows_x86_64_gnu 0.52.4", 3676 | "windows_x86_64_gnullvm 0.52.4", 3677 | "windows_x86_64_msvc 0.52.4", 3678 | ] 3679 | 3680 | [[package]] 3681 | name = "windows_aarch64_gnullvm" 3682 | version = "0.48.5" 3683 | source = "registry+https://github.com/rust-lang/crates.io-index" 3684 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 3685 | 3686 | [[package]] 3687 | name = "windows_aarch64_gnullvm" 3688 | version = "0.52.4" 3689 | source = "registry+https://github.com/rust-lang/crates.io-index" 3690 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 3691 | 3692 | [[package]] 3693 | name = "windows_aarch64_msvc" 3694 | version = "0.48.5" 3695 | source = "registry+https://github.com/rust-lang/crates.io-index" 3696 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 3697 | 3698 | [[package]] 3699 | name = "windows_aarch64_msvc" 3700 | version = "0.52.4" 3701 | source = "registry+https://github.com/rust-lang/crates.io-index" 3702 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 3703 | 3704 | [[package]] 3705 | name = "windows_i686_gnu" 3706 | version = "0.48.5" 3707 | source = "registry+https://github.com/rust-lang/crates.io-index" 3708 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 3709 | 3710 | [[package]] 3711 | name = "windows_i686_gnu" 3712 | version = "0.52.4" 3713 | source = "registry+https://github.com/rust-lang/crates.io-index" 3714 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 3715 | 3716 | [[package]] 3717 | name = "windows_i686_msvc" 3718 | version = "0.48.5" 3719 | source = "registry+https://github.com/rust-lang/crates.io-index" 3720 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 3721 | 3722 | [[package]] 3723 | name = "windows_i686_msvc" 3724 | version = "0.52.4" 3725 | source = "registry+https://github.com/rust-lang/crates.io-index" 3726 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 3727 | 3728 | [[package]] 3729 | name = "windows_x86_64_gnu" 3730 | version = "0.48.5" 3731 | source = "registry+https://github.com/rust-lang/crates.io-index" 3732 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 3733 | 3734 | [[package]] 3735 | name = "windows_x86_64_gnu" 3736 | version = "0.52.4" 3737 | source = "registry+https://github.com/rust-lang/crates.io-index" 3738 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 3739 | 3740 | [[package]] 3741 | name = "windows_x86_64_gnullvm" 3742 | version = "0.48.5" 3743 | source = "registry+https://github.com/rust-lang/crates.io-index" 3744 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 3745 | 3746 | [[package]] 3747 | name = "windows_x86_64_gnullvm" 3748 | version = "0.52.4" 3749 | source = "registry+https://github.com/rust-lang/crates.io-index" 3750 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 3751 | 3752 | [[package]] 3753 | name = "windows_x86_64_msvc" 3754 | version = "0.48.5" 3755 | source = "registry+https://github.com/rust-lang/crates.io-index" 3756 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 3757 | 3758 | [[package]] 3759 | name = "windows_x86_64_msvc" 3760 | version = "0.52.4" 3761 | source = "registry+https://github.com/rust-lang/crates.io-index" 3762 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 3763 | 3764 | [[package]] 3765 | name = "winnow" 3766 | version = "0.5.40" 3767 | source = "registry+https://github.com/rust-lang/crates.io-index" 3768 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 3769 | dependencies = [ 3770 | "memchr", 3771 | ] 3772 | 3773 | [[package]] 3774 | name = "winreg" 3775 | version = "0.50.0" 3776 | source = "registry+https://github.com/rust-lang/crates.io-index" 3777 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 3778 | dependencies = [ 3779 | "cfg-if", 3780 | "windows-sys 0.48.0", 3781 | ] 3782 | 3783 | [[package]] 3784 | name = "x25519-dalek" 3785 | version = "2.0.1" 3786 | source = "registry+https://github.com/rust-lang/crates.io-index" 3787 | checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" 3788 | dependencies = [ 3789 | "curve25519-dalek", 3790 | "rand_core", 3791 | "zeroize", 3792 | ] 3793 | 3794 | [[package]] 3795 | name = "xxhash-rust" 3796 | version = "0.8.10" 3797 | source = "registry+https://github.com/rust-lang/crates.io-index" 3798 | checksum = "927da81e25be1e1a2901d59b81b37dd2efd1fc9c9345a55007f09bf5a2d3ee03" 3799 | 3800 | [[package]] 3801 | name = "zeroize" 3802 | version = "1.7.0" 3803 | source = "registry+https://github.com/rust-lang/crates.io-index" 3804 | checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" 3805 | dependencies = [ 3806 | "zeroize_derive", 3807 | ] 3808 | 3809 | [[package]] 3810 | name = "zeroize_derive" 3811 | version = "1.4.2" 3812 | source = "registry+https://github.com/rust-lang/crates.io-index" 3813 | checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 3814 | dependencies = [ 3815 | "proc-macro2", 3816 | "quote", 3817 | "syn 2.0.52", 3818 | ] 3819 | 3820 | [[package]] 3821 | name = "zstd" 3822 | version = "0.13.0" 3823 | source = "registry+https://github.com/rust-lang/crates.io-index" 3824 | checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" 3825 | dependencies = [ 3826 | "zstd-safe", 3827 | ] 3828 | 3829 | [[package]] 3830 | name = "zstd-safe" 3831 | version = "7.0.0" 3832 | source = "registry+https://github.com/rust-lang/crates.io-index" 3833 | checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" 3834 | dependencies = [ 3835 | "zstd-sys", 3836 | ] 3837 | 3838 | [[package]] 3839 | name = "zstd-sys" 3840 | version = "2.0.9+zstd.1.5.5" 3841 | source = "registry+https://github.com/rust-lang/crates.io-index" 3842 | checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" 3843 | dependencies = [ 3844 | "cc", 3845 | "pkg-config", 3846 | ] 3847 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tfreg" 3 | description = "Registry serving terraform providers from github releases" 4 | version = "0.2.0" 5 | edition = "2021" 6 | license = "MIT" 7 | repository = "https://github.com/mattclement/tfreg" 8 | 9 | [dependencies] 10 | anyhow = "1.0.57" 11 | axum = { version = "0.7" } 12 | futures = "0.3.21" 13 | futures-util = "0.3" 14 | oauth2 = { version = "4.2.0" } 15 | 16 | octocrab = { version = "0.34", features = ["rustls"] } 17 | http-body = "1.0" 18 | http-body-util = "0.1" 19 | reqwest = { version = "0.11.10", features = ["stream"], default-features = false } 20 | url = "2.5" 21 | tower = "0.4.13" 22 | 23 | serde = { version = "1.0", features = ["derive"] } 24 | serde_json = "1.0" 25 | serde_urlencoded = "0.7.1" 26 | tokio = { version = "1.0", features = ["full"] } 27 | tokio-util = { version = "0.7.2", features = ["io"] } 28 | toml = "0.7" 29 | tower-http = { version = "0.5", features = ["trace", "compression-full", "fs"] } 30 | sequoia-openpgp = { version = "1.19.0", default-features = false, features = ["allow-experimental-crypto", "allow-variable-time-crypto", "crypto-rust"] } 31 | clap = { version = "3.2.6", features = ["derive", "env"] } 32 | thiserror = "1.0.31" 33 | orion = "0.17.1" 34 | base64 = "0.21" 35 | 36 | opentelemetry = "0.21" 37 | opentelemetry_sdk = { version = "0.21", features = ["rt-tokio"]} 38 | tracing-opentelemetry = "0.22" 39 | opentelemetry-otlp = { version = "0.14", features = ["http-proto", "reqwest-client"] } 40 | 41 | tracing = "0.1" 42 | tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } 43 | async-trait = "0.1.77" 44 | hex = "0.4.3" 45 | 46 | [profile.release] 47 | strip = "symbols" 48 | lto = "thin" 49 | 50 | [profile.dev] 51 | opt-level = 1 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matt Clement 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tfreg: Private Terraform Registry 2 | 3 | `tfreg` is a terraform registry server that serves assets stored in github releases. 4 | 5 | ![Demo terminal output of tfreg workflow](.github/images/demo.png) 6 | 7 | ## Features 8 | - An opinionated implementation of the terraform provider registry. See [required repo structure](#required-repo-structure) for more details. 9 | - A Terraform Login server. This is required for use with private providers, as it uses the github oauth2 flow to use your permissions for downloading assets. 10 | 11 | ## Installation 12 | Check the github [releases](https://github.com/mattclement/tfreg/releases) or clone the repo and run `cargo build --release`. 13 | 14 | ## Setup 15 | 1. [Generate a new oauth app](https://github.com/settings/applications/new) on GitHub. 16 | - The `Authorization callback URL` must be a `localhost` URL (e.g. `http://localhost:10000/callback`) since the terraform CLI runs an http server on localhost to receive the code from the redirect url. When the url is on localhost, the port may be changed by `tfreg` to facilitate this specific workflow. See the [github oauth2 docs](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#localhost-redirect-urls) for more information. 17 | 2. Generate a new client secret from your new oauth application. 18 | 4. Run the app: see the [configuration](#configuration) table below for how to set the necessary properties to run the app. 19 | - There is no support for serving HTTPS traffic, so you must run some sort of TLS-capable proxy in front, since the terraform CLI will only talk over HTTPS. 20 | 5. Test it: `curl localhost:8080/.well-known/terraform.json` (or whatever host/port you're running on) should return a static json payload for the Terraform CLI to interpret. 21 | 22 | ## Configuration 23 | 24 | Configuration values are listed below. **Required fields are shown in bold**. CLI args and environment variables will override values set in an optionally provided TOML configuration file. 25 | 26 | | Property | Example value | as CLI flag | as environment variable | as TOML property | 27 | | ----------------------------- | ----------------- | ------------------ | ----------------------- | ---------------- | 28 | | config file | `config.toml` | `--config` | `TFREG_CONFIG` | N/A | 29 | | listen addr | `127.0.0.1:8080` | `--addr` | `TFREG_ADDR` | `addr` | 30 | | rust log filter | `tfreg=debug` | `--log-level` | `TFREG_LOG_LEVEL` | `log_level` | 31 | | stdout log format | `pretty` | `--log-format` | `TFREG_LOG_FORMAT` | `log_format` | 32 | | OTLP trace collector URL | `localhost:4317` | `--otlp-endpoint` | `TFREG_OTLP_ENDPOINT` | `otlp_endpoint` | 33 | | OTLP trace collector headers | `foo=bar,baz=qux` | `--otlp-headers` | `TFREG_OTLP_HEADERS` | `otlp_headers` | 34 | | cache directory | `./cache` | `--cache-dir` | `TFREG_CACHE_DIR` | `cache_dir` | 35 | | **oauth2 client id** | `abcdef1234` | `--client-id` | `TFREG_CLIENT_ID` | `client_id` | 36 | | **oauth2 client secret** | `abcdef1234` | `--client-secret` | `TFREG_CLIENT_SECRET` | `client_secret` | 37 | | **secret key (32 bytes)** | `abcdef1234` | `--secret-key` | `TFREG_SECRET_KEY` | `secret_key` | 38 | 39 | Note: the rust log filter variable follows the [env logger syntax](https://docs.rs/env_logger/0.9.0/env_logger/#enabling-logging). 40 | 41 | ## Required repo structure 42 | 43 | A single version of a terraform provider corresponds to a github release. 44 | 45 | The [`v0.3.1` tagged release of `terraform-provider-cortex`](https://github.com/cortexapps/terraform-provider-cortex/releases/tag/v0.3.1) is possible to serve with `tfreg` as it follows the [repo structure rules](#repo-structure-rules): 46 | ``` 47 | terraform-provider-cortex_0.3.1_darwin_amd64.zip 48 | terraform-provider-cortex_0.3.1_darwin_arm64.zip 49 | terraform-provider-cortex_0.3.1_freebsd_386.zip 50 | terraform-provider-cortex_0.3.1_freebsd_amd64.zip 51 | terraform-provider-cortex_0.3.1_freebsd_arm.zip 52 | terraform-provider-cortex_0.3.1_freebsd_arm64.zip 53 | terraform-provider-cortex_0.3.1_linux_386.zip 54 | terraform-provider-cortex_0.3.1_linux_amd64.zip 55 | terraform-provider-cortex_0.3.1_linux_arm.zip 56 | terraform-provider-cortex_0.3.1_linux_arm64.zip 57 | terraform-provider-cortex_0.3.1_SHA256SUMS 58 | terraform-provider-cortex_0.3.1_SHA256SUMS.sig 59 | terraform-provider-cortex_0.3.1_windows_386.zip 60 | terraform-provider-cortex_0.3.1_windows_amd64.zip 61 | terraform-provider-cortex_0.3.1_windows_arm.zip 62 | terraform-provider-cortex_0.3.1_windows_arm64.zip 63 | ``` 64 | 65 | ### Repo structure rules 66 | - Releases must be tagged as either `v1.2.3` or `1.2.3`. 67 | - There may be a file named `SHA256SUMS` or `-_SHA256SUMS`. **If a binary zip is not listed in this file, it will not be available for download.** 68 | - All binary files must be named as above: `___.zip`. `version` must not contain a `v` prefix. 69 | - Zip files must contain only the binary which must be named the same as the archive, without the `.zip` suffix. 70 | 71 | The GPG Key used to sign the sha256sums file is (currently) generated by `tfreg` on startup. It is not required to add a `SHA256SUMS.sig` file, as `tfreg` signs the `SHA256SUMS` file at request-time. 72 | -------------------------------------------------------------------------------- /src/app_config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use serde::{Deserialize, Serialize}; 4 | use std::{env, fs, net::SocketAddr, path::PathBuf}; 5 | 6 | #[derive(Debug, Serialize, Deserialize, PartialEq)] 7 | pub struct AppConfig { 8 | pub client_id: String, 9 | pub client_secret: String, 10 | pub addr: SocketAddr, 11 | pub log_level: String, 12 | pub cache_dir: PathBuf, 13 | pub secret_key: String, 14 | pub otlp_endpoint: Option, 15 | pub otlp_headers: Option, 16 | pub log_format: LogFormat, 17 | } 18 | 19 | impl AppConfig { 20 | pub fn merge_source_config(&mut self, c: ConfigSource) { 21 | if let Some(addr) = c.addr { 22 | self.addr = addr; 23 | } 24 | if let Some(log_level) = c.log_level { 25 | self.log_level = log_level; 26 | } 27 | if let Some(log_format) = c.log_format { 28 | self.log_format = log_format; 29 | } 30 | if let Some(cache_dir) = c.cache_dir { 31 | self.cache_dir = cache_dir; 32 | } 33 | if let Some(client_id) = c.client_id { 34 | self.client_id = client_id; 35 | } 36 | if let Some(client_secret) = c.client_secret { 37 | self.client_secret = client_secret; 38 | } 39 | if let Some(secret_key) = c.secret_key { 40 | self.secret_key = secret_key; 41 | } 42 | if let Some(otlp_endpoint) = c.otlp_endpoint { 43 | self.otlp_endpoint = Some(otlp_endpoint); 44 | } 45 | if let Some(otlp_headers) = c.otlp_headers { 46 | self.otlp_headers = Some(otlp_headers); 47 | } 48 | } 49 | 50 | pub fn validate(&self) -> Result<()> { 51 | if self.client_id.is_empty() { 52 | anyhow::bail!("Client ID must be set"); 53 | } 54 | if self.client_secret.is_empty() { 55 | anyhow::bail!("Client secret must be set"); 56 | } 57 | Ok(()) 58 | } 59 | } 60 | 61 | impl Default for AppConfig { 62 | fn default() -> Self { 63 | let cache_dir = match env::current_dir() { 64 | Ok(dir) => dir, 65 | Err(_) => env::temp_dir(), 66 | } 67 | .join("cache"); 68 | Self { 69 | client_id: "".to_string(), 70 | client_secret: "".to_string(), 71 | addr: SocketAddr::from(([127, 0, 0, 1], 8080)), 72 | log_level: "tfreg=debug,tower_http=debug".into(), 73 | cache_dir, 74 | secret_key: "".to_string(), 75 | otlp_endpoint: None, 76 | otlp_headers: None, 77 | log_format: LogFormat::Compact, 78 | } 79 | } 80 | } 81 | 82 | #[derive(Debug, Clone, Deserialize, Serialize, clap::ValueEnum, PartialEq)] 83 | pub enum LogFormat { 84 | Compact, 85 | Pretty, 86 | Json, 87 | } 88 | 89 | #[derive(Parser, Serialize, Deserialize, Debug, Default)] 90 | #[clap(author, version, about)] 91 | pub struct ConfigSource { 92 | /// TOML configuration file to read 93 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_CONFIG")] 94 | pub config: Option, 95 | 96 | /// socket address to listen on 97 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_ADDR")] 98 | pub addr: Option, 99 | 100 | /// Set log level in RUST_LOG format 101 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_LOG_LEVEL")] 102 | pub log_level: Option, 103 | 104 | /// Log format to use on stdout. 105 | #[clap(long, hide_env_values(true), value_enum, env = "TFREG_LOG_FORMAT")] 106 | pub log_format: Option, 107 | 108 | /// Directory to cache downloadable assets in 109 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_CACHE_DIR")] 110 | pub cache_dir: Option, 111 | 112 | /// Github OAuth2 app client id 113 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_CLIENT_ID")] 114 | pub client_id: Option, 115 | 116 | /// Github OAuth2 app client secret 117 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_CLIENT_SECRET")] 118 | pub client_secret: Option, 119 | 120 | /// 32 byte secret key used for token encryption 121 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_SECRET_KEY")] 122 | pub secret_key: Option, 123 | 124 | /// URL to send OTLP traces to. Will only send traces if this property is specified. 125 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_OTLP_ENDPOINT")] 126 | pub otlp_endpoint: Option, 127 | 128 | /// Additional headers in k=v,k=v format to send with OTLP traces. 129 | #[clap(long, hide_env_values(true), value_parser, env = "TFREG_OTLP_HEADERS")] 130 | pub otlp_headers: Option, 131 | } 132 | 133 | pub fn load() -> Result { 134 | let cli_args = ConfigSource::parse(); 135 | let mut conf = AppConfig::default(); 136 | 137 | if let Some(p) = &cli_args.config { 138 | let file_conf: ConfigSource = toml::from_str(&fs::read_to_string(p)?)?; 139 | conf.merge_source_config(file_conf); 140 | } 141 | 142 | conf.merge_source_config(cli_args); 143 | conf.validate()?; 144 | 145 | Ok(conf) 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use super::*; 151 | 152 | #[test] 153 | fn merge_none_does_nothing() { 154 | let mut base = AppConfig::default(); 155 | let layer = ConfigSource { 156 | ..Default::default() 157 | }; 158 | base.merge_source_config(layer); 159 | assert_eq!(base, AppConfig::default()); 160 | } 161 | 162 | #[test] 163 | fn merge_some_unwraps_and_overwrites() { 164 | let mut base = AppConfig::default(); 165 | let layer = ConfigSource { 166 | log_level: Some("test".to_string()), 167 | ..Default::default() 168 | }; 169 | base.merge_source_config(layer); 170 | assert_ne!(base, AppConfig::default()); 171 | assert_eq!(base.log_level, "test".to_string()); 172 | } 173 | 174 | #[test] 175 | fn merge_multiple_layers() { 176 | let mut base = AppConfig::default(); 177 | let layer = ConfigSource { 178 | log_level: Some("test".to_string()), 179 | ..Default::default() 180 | }; 181 | let second_layer = ConfigSource { 182 | log_level: Some("second_test".to_string()), 183 | client_id: Some("id".to_string()), 184 | ..Default::default() 185 | }; 186 | 187 | base.merge_source_config(layer); 188 | assert_ne!(base, AppConfig::default()); 189 | assert_eq!(base.log_level, "test".to_string()); 190 | assert!(base.client_id.is_empty()); 191 | 192 | base.merge_source_config(second_layer); 193 | assert_eq!(base.log_level, "second_test".to_string()); 194 | assert_eq!(base.client_id, "id".to_string()); 195 | } 196 | 197 | #[test] 198 | fn validate_fails_with_missing_client_id() { 199 | let base = AppConfig::default(); 200 | let res = base.validate(); 201 | assert!(res.is_err()); 202 | assert_eq!(res.unwrap_err().to_string(), "Client ID must be set"); 203 | } 204 | 205 | #[test] 206 | fn validate_fails_with_missing_client_secret() { 207 | let base = AppConfig { 208 | client_id: "not_empty".to_string(), 209 | ..Default::default() 210 | }; 211 | let res = base.validate(); 212 | assert!(res.is_err()); 213 | assert_eq!(res.unwrap_err().to_string(), "Client secret must be set"); 214 | } 215 | 216 | #[test] 217 | fn validate_passes_with_both_client_fields() { 218 | let base = AppConfig { 219 | client_id: "not_empty".to_string(), 220 | client_secret: "not_empty".to_string(), 221 | ..Default::default() 222 | }; 223 | let res = base.validate(); 224 | assert!(res.is_ok()); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/app_tracing.rs: -------------------------------------------------------------------------------- 1 | /// Most of this code is from https://github.com/tokio-rs/axum/pull/769. 2 | use anyhow::Result; 3 | use axum::http::{header, HeaderMap, Method, Request}; 4 | use axum::{ 5 | extract::{ConnectInfo, MatchedPath, OriginalUri}, 6 | response::Response, 7 | }; 8 | use opentelemetry::KeyValue; 9 | use opentelemetry_otlp::WithExportConfig; 10 | use opentelemetry_sdk::{ 11 | trace::{self, Sampler}, 12 | Resource, 13 | }; 14 | use std::collections::HashMap; 15 | use std::{borrow::Cow, net::SocketAddr, time::Duration}; 16 | use tower_http::{ 17 | classify::{ServerErrorsAsFailures, ServerErrorsFailureClass, SharedClassifier}, 18 | trace::{MakeSpan, OnBodyChunk, OnEos, OnFailure, OnRequest, OnResponse, TraceLayer}, 19 | }; 20 | use tracing::{field::Empty, Span}; 21 | use tracing_subscriber::Layer; 22 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; 23 | 24 | use crate::app_config::{AppConfig, LogFormat}; 25 | 26 | pub fn init(config: &AppConfig) -> Result<()> { 27 | if config.log_level == "disabled" { 28 | return Ok(()); 29 | } 30 | 31 | let registry = tracing_subscriber::registry() 32 | .with(logging_layer(&config.log_format)) 33 | .with(tracing_subscriber::EnvFilter::new(&config.log_level)); 34 | 35 | let Some(otlp_endpoint) = &config.otlp_endpoint else { 36 | return Ok(registry.try_init()?); 37 | }; 38 | 39 | let mut map = HashMap::new(); 40 | if let Some(headers) = &config.otlp_headers { 41 | for pair in headers.split(',') { 42 | let kv: Vec<&str> = pair.split('=').collect(); 43 | if kv.len() == 2 { 44 | map.insert(kv[0].to_string(), kv[1].to_string()); 45 | } 46 | } 47 | } 48 | 49 | let exporter = opentelemetry_otlp::new_exporter() 50 | .http() 51 | .with_endpoint(otlp_endpoint) 52 | .with_headers(map); 53 | 54 | let trace_config = trace::config() 55 | .with_sampler(Sampler::AlwaysOn) 56 | .with_resource(Resource::new(vec![KeyValue::new("service.name", "tfreg")])); 57 | 58 | let tracer = opentelemetry_otlp::new_pipeline() 59 | .tracing() 60 | .with_exporter(exporter) 61 | .with_trace_config(trace_config) 62 | .install_batch(opentelemetry_sdk::runtime::Tokio)?; 63 | 64 | Ok(registry 65 | .with(tracing_opentelemetry::layer().with_tracer(tracer)) 66 | .try_init()?) 67 | } 68 | 69 | /// Construct a fmt layer based on the logging format requested. 70 | /// 71 | /// Ref: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#runtime-configuration-with-layers 72 | fn logging_layer(log_format: &LogFormat) -> Box + Send + Sync> 73 | where 74 | S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>, 75 | { 76 | let json = tracing_subscriber::fmt::layer() 77 | .json() 78 | .with_current_span(true) 79 | .flatten_event(true) 80 | .with_target(false) 81 | .with_span_list(false) 82 | .boxed(); 83 | let pretty = tracing_subscriber::fmt::layer().pretty().boxed(); 84 | let compact = tracing_subscriber::fmt::layer().compact().boxed(); 85 | match log_format { 86 | LogFormat::Compact => compact, 87 | LogFormat::Pretty => pretty, 88 | LogFormat::Json => json, 89 | } 90 | } 91 | 92 | /// OpenTelemetry tracing middleware. 93 | /// 94 | /// This returns a [`TraceLayer`] configured to use [OpenTelemetry's conventional span field 95 | /// names][otel]. 96 | /// 97 | /// # Span fields 98 | /// 99 | /// The following fields will be set on the span: 100 | /// 101 | /// - `http.client_ip`: The client's IP address. Requires using 102 | /// [`Router::into_make_service_with_connect_info`] 103 | /// - `http.host`: The value of the `Host` header 104 | /// - `http.method`: The request method 105 | /// - `http.route`: The matched route 106 | /// - `http.scheme`: The URI scheme used (`HTTP` or `HTTPS`) 107 | /// - `http.status_code`: The response status code 108 | /// - `http.target`: The full request target including path and query parameters 109 | /// - `http.user_agent`: The value of the `User-Agent` header 110 | /// - `otel.kind`: Always `server` 111 | /// - `otel.status_code`: `OK` if the response is success, `ERROR` if it is a 5xx 112 | /// - `trace_id`: The trace id as tracted via the remote span context. 113 | /// 114 | pub fn opentelemetry_tracing_layer() -> TraceLayer< 115 | SharedClassifier, 116 | OtelMakeSpan, 117 | OtelOnRequest, 118 | OtelOnResponse, 119 | OtelOnBodyChunk, 120 | OtelOnEos, 121 | OtelOnFailure, 122 | > { 123 | TraceLayer::new_for_http() 124 | .make_span_with(OtelMakeSpan) 125 | .on_request(OtelOnRequest) 126 | .on_response(OtelOnResponse) 127 | .on_body_chunk(OtelOnBodyChunk) 128 | .on_eos(OtelOnEos) 129 | .on_failure(OtelOnFailure) 130 | } 131 | 132 | /// A [`MakeSpan`] that creates tracing spans using [OpenTelemetry's conventional field names][otel]. 133 | /// 134 | /// [otel]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md 135 | #[derive(Clone, Copy, Debug)] 136 | pub struct OtelMakeSpan; 137 | 138 | impl MakeSpan for OtelMakeSpan { 139 | fn make_span(&mut self, req: &Request) -> Span { 140 | let user_agent = req 141 | .headers() 142 | .get(header::USER_AGENT) 143 | .map_or("", |h| h.to_str().unwrap_or("")); 144 | 145 | let host = req 146 | .headers() 147 | .get(header::HOST) 148 | .map_or("", |h| h.to_str().unwrap_or("")); 149 | 150 | let http_route = if let Some(matched_path) = req.extensions().get::() { 151 | matched_path.as_str().to_owned() 152 | } else if let Some(uri) = req.extensions().get::() { 153 | uri.0.path().to_owned() 154 | } else { 155 | req.uri().path().to_owned() 156 | }; 157 | 158 | let uri = if let Some(uri) = req.extensions().get::() { 159 | uri.0.clone() 160 | } else { 161 | req.uri().clone() 162 | }; 163 | let http_target = uri.path(); 164 | 165 | let client_ip = parse_x_forwarded_for(req.headers()) 166 | .or_else(|| { 167 | req.extensions() 168 | .get::>() 169 | .map(|ConnectInfo(client_ip)| Cow::from(client_ip.to_string())) 170 | }) 171 | .unwrap_or_default(); 172 | 173 | let remote_context = extract_remote_context(req.headers()); 174 | let span = tracing::info_span!( 175 | "HTTP request", 176 | http.client_ip = %client_ip, 177 | http.host = %host, 178 | http.method = %http_method(req.method()), 179 | http.route = %http_route, 180 | http.status_code = Empty, 181 | http.target = %http_target, 182 | http.user_agent = %user_agent, 183 | otel.kind = "server", 184 | otel.status_code = Empty, 185 | ); 186 | 187 | tracing_opentelemetry::OpenTelemetrySpanExt::set_parent(&span, remote_context); 188 | 189 | span 190 | } 191 | } 192 | 193 | fn parse_x_forwarded_for(headers: &HeaderMap) -> Option> { 194 | let value = headers.get("x-forwarded-for")?; 195 | let value = value.to_str().ok()?; 196 | let mut ips = value.split(','); 197 | Some(ips.next()?.trim().into()) 198 | } 199 | 200 | fn http_method(method: &Method) -> Cow<'static, str> { 201 | match method { 202 | &Method::CONNECT => "CONNECT".into(), 203 | &Method::DELETE => "DELETE".into(), 204 | &Method::GET => "GET".into(), 205 | &Method::HEAD => "HEAD".into(), 206 | &Method::OPTIONS => "OPTIONS".into(), 207 | &Method::PATCH => "PATCH".into(), 208 | &Method::POST => "POST".into(), 209 | &Method::PUT => "PUT".into(), 210 | &Method::TRACE => "TRACE".into(), 211 | other => other.to_string().into(), 212 | } 213 | } 214 | 215 | // If remote request has no span data the propagator defaults to an unsampled context 216 | fn extract_remote_context(headers: &axum::http::HeaderMap) -> opentelemetry::Context { 217 | struct HeaderExtractor<'a>(&'a axum::http::HeaderMap); 218 | 219 | impl<'a> opentelemetry::propagation::Extractor for HeaderExtractor<'a> { 220 | fn get(&self, key: &str) -> Option<&str> { 221 | self.0.get(key).and_then(|value| value.to_str().ok()) 222 | } 223 | 224 | fn keys(&self) -> Vec<&str> { 225 | self.0.keys().map(|value| value.as_str()).collect() 226 | } 227 | } 228 | 229 | let extractor = HeaderExtractor(headers); 230 | opentelemetry::global::get_text_map_propagator(|propagator| propagator.extract(&extractor)) 231 | } 232 | 233 | /// Callback that [`Trace`] will call when it receives a request. 234 | /// 235 | /// [`Trace`]: tower_http::trace::Trace 236 | #[derive(Clone, Copy, Debug)] 237 | pub struct OtelOnRequest; 238 | 239 | impl OnRequest for OtelOnRequest { 240 | #[inline] 241 | fn on_request(&mut self, _request: &Request, _span: &Span) {} 242 | } 243 | 244 | /// Callback that [`Trace`] will call when it receives a response. 245 | /// 246 | /// [`Trace`]: tower_http::trace::Trace 247 | #[derive(Clone, Copy, Debug)] 248 | pub struct OtelOnResponse; 249 | 250 | impl OnResponse for OtelOnResponse { 251 | fn on_response(self, response: &Response, _latency: Duration, span: &Span) { 252 | let status = response.status().as_u16().to_string(); 253 | span.record("http.status_code", &tracing::field::display(status)); 254 | 255 | // assume there is no error, if there is `OtelOnFailure` will be called and override this 256 | span.record("otel.status_code", "OK"); 257 | tracing::info!("Request finished"); 258 | } 259 | } 260 | 261 | /// Callback that [`Trace`] will call when the response body produces a chunk. 262 | /// 263 | /// [`Trace`]: tower_http::trace::Trace 264 | #[derive(Clone, Copy, Debug)] 265 | pub struct OtelOnBodyChunk; 266 | 267 | impl OnBodyChunk for OtelOnBodyChunk { 268 | #[inline] 269 | fn on_body_chunk(&mut self, _chunk: &B, _latency: Duration, _span: &Span) {} 270 | } 271 | 272 | /// Callback that [`Trace`] will call when a streaming response completes. 273 | /// 274 | /// [`Trace`]: tower_http::trace::Trace 275 | #[derive(Clone, Copy, Debug)] 276 | pub struct OtelOnEos; 277 | 278 | impl OnEos for OtelOnEos { 279 | #[inline] 280 | fn on_eos( 281 | self, 282 | _trailers: Option<&axum::http::HeaderMap>, 283 | _stream_duration: Duration, 284 | _span: &Span, 285 | ) { 286 | } 287 | } 288 | 289 | /// Callback that [`Trace`] will call when a response or end-of-stream is classified as a failure. 290 | /// 291 | /// [`Trace`]: tower_http::trace::Trace 292 | #[derive(Clone, Copy, Debug)] 293 | pub struct OtelOnFailure; 294 | 295 | impl OnFailure for OtelOnFailure { 296 | fn on_failure(&mut self, failure: ServerErrorsFailureClass, _latency: Duration, span: &Span) { 297 | span.record("error", true); 298 | match failure { 299 | ServerErrorsFailureClass::StatusCode(status) => { 300 | if status.is_server_error() { 301 | span.record("otel.status_code", "ERROR"); 302 | } 303 | } 304 | ServerErrorsFailureClass::Error(_) => { 305 | span.record("otel.status_code", "ERROR"); 306 | } 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/auth.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::oauth::Authenticate; 4 | use anyhow::Result; 5 | use axum::{ 6 | extract::{Form, Query}, 7 | http::StatusCode, 8 | response::Redirect, 9 | routing::{get, post}, 10 | Extension, Json, Router, 11 | }; 12 | use serde_json::{json, Value}; 13 | use tracing::error; 14 | 15 | use crate::oauth::{types::AuthFlow, OAuth2Error, TokenRequest}; 16 | 17 | pub fn router(auth: Arc) -> Result { 18 | Ok(Router::new() 19 | .route("/authz", get(auth_flow_handler::)) 20 | .route("/token", post(token_handler::)) 21 | .layer(Extension(auth))) 22 | } 23 | 24 | async fn auth_flow_handler( 25 | Query(params): Query, 26 | Extension(authenticator): Extension>, 27 | ) -> Result { 28 | match authenticator.start_auth_flow(params).await { 29 | Ok(url) => Ok(Redirect::to(&url)), 30 | Err(OAuth2Error::PkceValidation(e)) => { 31 | error!("{}", e); 32 | Err(StatusCode::BAD_REQUEST) 33 | } 34 | Err(e) => { 35 | error!("{}", e); 36 | Err(StatusCode::INTERNAL_SERVER_ERROR) 37 | } 38 | } 39 | } 40 | 41 | async fn token_handler( 42 | Extension(authenticator): Extension>, 43 | Form(params): Form, 44 | ) -> Result, StatusCode> { 45 | match authenticator.exchange_code_for_token(params).await { 46 | Ok(c) => Ok(Json(json!({ "access_token": c }))), 47 | Err(e) => { 48 | error!("{}", e); 49 | Err(StatusCode::BAD_REQUEST) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/download.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::{Extension, Router}; 4 | use tower_http::compression::CompressionLayer; 5 | use tower_http::services::ServeDir; 6 | 7 | use crate::middleware; 8 | use crate::oauth::Authenticate; 9 | 10 | pub fn router( 11 | config: &crate::app_config::AppConfig, 12 | path: &str, 13 | auth: Arc, 14 | ) -> Router { 15 | let query_middleware = axum::middleware::from_fn(middleware::query_param_auth::); 16 | Router::new() 17 | .nest_service(path, ServeDir::new(&config.cache_dir)) 18 | .route_layer(query_middleware) 19 | .layer(CompressionLayer::new()) 20 | .layer(Extension(auth)) 21 | } 22 | -------------------------------------------------------------------------------- /src/github/cache.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use std::path::PathBuf; 3 | use tokio::fs; 4 | use tracing::debug; 5 | 6 | use super::{ReleaseTag, Repo}; 7 | 8 | #[derive(Debug)] 9 | pub struct AssetCache { 10 | base_dir: PathBuf, 11 | } 12 | 13 | impl AssetCache { 14 | pub async fn new(base_dir: PathBuf) -> Result { 15 | let cache = Self { base_dir }; 16 | cache.create_cache_dir().await?; 17 | Ok(cache) 18 | } 19 | 20 | async fn create_cache_dir(&self) -> Result<()> { 21 | debug!("Creating cache_dir at {}", self.base_dir.display()); 22 | fs::create_dir_all(&self.base_dir) 23 | .await 24 | .map_err(|err| anyhow!(err)) 25 | } 26 | 27 | pub fn build_asset_path(&self, repo: &Repo, tag: &ReleaseTag, filename: &str) -> PathBuf { 28 | self.base_dir 29 | .clone() 30 | .as_path() 31 | .join(&repo.org) 32 | .join(&repo.name) 33 | .join(tag) 34 | .join(filename) 35 | } 36 | 37 | pub async fn get_asset(&self, repo: &Repo, tag: &ReleaseTag, name: &str) -> Result> { 38 | let path = self.build_asset_path(repo, &tag.trim_start_matches('v').to_string(), name); 39 | Ok(fs::read(path).await?) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/github/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use futures::{future::join_all, stream::TryStreamExt}; 3 | use octocrab::{models::repos::Release, Page}; 4 | use reqwest::Url; 5 | use serde::{Deserialize, Serialize}; 6 | use std::{ 7 | collections::HashMap, 8 | io::ErrorKind, 9 | path::{Path, PathBuf}, 10 | }; 11 | use tokio::sync::RwLock; 12 | use tokio_util::io::StreamReader; 13 | use tracing::{error, info, instrument}; 14 | 15 | use self::cache::AssetCache; 16 | 17 | mod cache; 18 | 19 | type ReleaseTag = String; 20 | type BinaryName = String; 21 | type Checksum = String; 22 | type Assets = HashMap>; 23 | 24 | #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] 25 | pub struct Repo { 26 | pub org: String, 27 | pub name: String, 28 | } 29 | 30 | impl Repo { 31 | pub fn new(organization: String, mut repository: String) -> Self { 32 | if !repository.starts_with("terraform-provider-") { 33 | repository = format!("terraform-provider-{}", repository); 34 | }; 35 | Self { 36 | org: organization, 37 | name: repository, 38 | } 39 | } 40 | } 41 | 42 | #[derive(Debug)] 43 | pub struct Client { 44 | /// List of assets by repo that are available for download. We store them here so that we don't 45 | /// have to re-download the entire list on each /download call. Since our flow assumes that the 46 | /// terraform CLI is calling it and as such will ask for /versions before /download, we re-fetch 47 | /// on /versions so that changed versions appear as such to /download. 48 | pub assets: RwLock>, 49 | 50 | /// A reference to a local file based cache directory. 51 | pub cache: AssetCache, 52 | 53 | /// TODO: is this used? 54 | pub signing_key: GPGKey, 55 | } 56 | 57 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] 58 | pub struct OsArch { 59 | pub os: String, 60 | pub arch: String, 61 | } 62 | 63 | #[derive(Debug, Serialize, Deserialize, Clone)] 64 | pub struct ProviderVersion { 65 | pub version: String, 66 | pub protocols: Vec, 67 | pub platforms: Vec, 68 | } 69 | 70 | #[derive(Debug, Serialize, Deserialize, Clone)] 71 | pub struct Download { 72 | pub signing_keys: SigningKey, 73 | 74 | #[serde(flatten)] 75 | pub asset: ProviderAsset, 76 | } 77 | 78 | #[derive(Debug, Serialize, Deserialize, Clone)] 79 | pub struct ProviderAsset { 80 | #[serde(flatten)] 81 | pub os_arch: OsArch, 82 | 83 | pub filename: String, 84 | pub download_url: String, 85 | pub shasum: String, 86 | pub shasums_url: String, 87 | pub shasums_signature_url: String, 88 | pub protocols: Vec, 89 | 90 | // used to download asset when download metadata is requested by the user. 91 | github_asset_url: reqwest::Url, 92 | } 93 | 94 | #[derive(Debug, Serialize, Deserialize, Clone)] 95 | pub struct SigningKey { 96 | pub gpg_public_keys: Vec, 97 | } 98 | 99 | #[derive(Debug, Serialize, Deserialize, Clone)] 100 | pub struct GPGKey { 101 | pub key_id: String, 102 | pub ascii_armor: String, 103 | } 104 | 105 | impl GPGKey { 106 | pub fn new(key_id: String, ascii_armor: String) -> Self { 107 | Self { 108 | key_id, 109 | ascii_armor, 110 | } 111 | } 112 | } 113 | 114 | // Client contains a local registry and cache of terraform provider binaries. 115 | impl Client { 116 | pub async fn new(cache_dir: PathBuf, signing_key: GPGKey) -> Result { 117 | Ok(Self { 118 | assets: RwLock::new(HashMap::new()), 119 | cache: AssetCache::new(cache_dir).await?, 120 | signing_key, 121 | }) 122 | } 123 | 124 | /// Return a list of available provider versions for a given repository. 125 | #[instrument(skip_all)] 126 | pub async fn get_versions( 127 | &self, 128 | token: String, 129 | github_org: String, 130 | repo_name: String, 131 | ) -> Option> { 132 | let key = Repo::new(github_org, repo_name); 133 | 134 | let assets_map = self 135 | .load_available_versions(token, key.clone()) 136 | .await 137 | .ok()?; 138 | 139 | let versions = assets_map 140 | .iter() 141 | .map(|(version, assets)| ProviderVersion { 142 | version: version.to_string(), 143 | protocols: vec!["5.0".to_string()], 144 | platforms: assets.iter().map(|x| x.os_arch.clone()).collect(), 145 | }) 146 | .collect::>(); 147 | 148 | // Put the map into an in-memory cache so we don't have to rebuild it when the user tries 149 | // to download a specific version. 150 | self.assets.write().await.insert(key, assets_map); 151 | 152 | if versions.is_empty() { 153 | return None; 154 | } 155 | Some(versions) 156 | } 157 | 158 | /// Return information on a single provider. 159 | #[instrument(skip_all)] 160 | pub async fn get_provider_info( 161 | &self, 162 | token: String, 163 | namespace: String, 164 | provider: String, 165 | version: String, 166 | os: String, 167 | arch: String, 168 | ) -> Option { 169 | let key = Repo::new(namespace, provider); 170 | let requested_os_arch = OsArch { os, arch }; 171 | let asset = self 172 | .assets 173 | .read() 174 | .await 175 | .get(&key)? 176 | .get(version.trim_start_matches('v'))? 177 | .iter() 178 | .find(|asset| asset.os_arch == requested_os_arch)? 179 | .clone(); 180 | 181 | if let Err(e) = self.download_asset(token, key, version, &asset).await { 182 | error!("{}", e); 183 | return None; 184 | } 185 | 186 | Some(Download { 187 | signing_keys: SigningKey { 188 | gpg_public_keys: vec![self.signing_key.clone()], 189 | }, 190 | asset, 191 | }) 192 | } 193 | 194 | /// Load all available provider versions for a given repo. A version is determined to be 195 | /// available by the following criteria: 196 | /// 197 | /// 1. There is a github release with a tag that follows a narrow subset of semver: x.y.z with 198 | /// an optional `v` prefix (e.g. `v1.2.3` or `1.2.3`). 199 | /// 2. There is a checksum file provided as a release asset. The checksum file itself may be 200 | /// named either `SHA256SUMS` or `terraform-provider-xyz_1.2.3_SHA256SUMS` where the 201 | /// provider name matches the repo name and the version matches the tag (no `v` allowed). 202 | /// 3. Downloadable assets are zip files with the following scheme: 203 | /// `terraform-provider-xyz_1.2.3_linux_amd64.zip` where all underscore-separted components 204 | /// indicate which (repo / version / operating system / architecture) the zipped asset 205 | /// contains. 206 | #[instrument(skip_all)] 207 | async fn load_available_versions(&self, token: String, repo: Repo) -> Result { 208 | let releases = self.load_release_metadata(token.clone(), &repo).await?; 209 | 210 | let mut futures = vec![]; 211 | 212 | for release in releases 213 | .into_iter() 214 | .filter(|r| r.assets.iter().any(|a| a.name.ends_with("SHA256SUMS"))) 215 | { 216 | futures.push(self.find_release_assets_from_checksum(&token, &repo, release)); 217 | } 218 | 219 | let res = join_all(futures).await; 220 | 221 | Ok(res.into_iter().filter_map(|x| x.ok()).collect::()) 222 | } 223 | 224 | #[instrument(skip_all)] 225 | async fn find_release_assets_from_checksum( 226 | &self, 227 | token: &str, 228 | repo: &Repo, 229 | release: Release, 230 | ) -> Result<(ReleaseTag, Vec)> { 231 | let mut assets = vec![]; 232 | self.download_release_sha256sums(token.to_string(), repo, &release) 233 | .await?; 234 | let checksums = self.parse_checksums(repo, &release).await?; 235 | let version = release.tag_name.trim_start_matches('v'); 236 | let download_base = Path::new("/downloads") 237 | .join(&repo.org) 238 | .join(&repo.name) 239 | .join(version); 240 | 241 | for release_asset in &release.assets { 242 | let shasum = match checksums.get(&release_asset.name) { 243 | Some(s) => s.to_string(), 244 | None => continue, 245 | }; 246 | 247 | // Asset name must be binary-name_version_os_arch.zip 248 | // e.g. terraform-provider-random_1.0.0_linux_amd64.zip 249 | let parts: Vec<&str> = release_asset 250 | .name 251 | .trim_end_matches(".zip") 252 | .rsplitn(3, '_') 253 | .collect(); 254 | let arch = parts.first(); 255 | let os = parts.get(1); 256 | if os.is_none() || arch.is_none() { 257 | continue; 258 | } 259 | 260 | // This points to our signing url that fetches the release's sha256sum and signs it 261 | // with an in-memory gpg key generated on startup. 262 | let sig_url = format!( 263 | "/providers/v1/{}/{}/{}/signature", 264 | repo.org, repo.name, version 265 | ); 266 | 267 | let asset = ProviderAsset { 268 | protocols: vec!["5.0".to_string()], // TODO: does this come from maniftest.json? 269 | os_arch: OsArch { 270 | os: os.unwrap().to_string(), 271 | arch: arch.unwrap().to_string(), 272 | }, 273 | filename: release_asset.name.clone(), 274 | download_url: download_base 275 | .join(&release_asset.name) 276 | .display() 277 | .to_string(), 278 | shasum, 279 | shasums_url: download_base.join("SHA256SUMS").display().to_string(), 280 | shasums_signature_url: sig_url.to_string(), 281 | github_asset_url: release_asset.browser_download_url.clone(), 282 | }; 283 | assets.push(asset); 284 | } 285 | Ok((release.tag_name.trim_start_matches('v').to_string(), assets)) 286 | } 287 | 288 | // Load releases from github. 289 | #[instrument(skip(self, token))] 290 | async fn load_release_metadata(&self, token: String, repo: &Repo) -> Result> { 291 | let client = octocrab::Octocrab::builder() 292 | .personal_token(token) 293 | .build()?; 294 | 295 | client 296 | .repos(&repo.org, &repo.name) 297 | .releases() 298 | .list() 299 | .per_page(100) 300 | .page(1u32) 301 | .send() 302 | .await 303 | .map_err(|e| anyhow!(e)) 304 | } 305 | 306 | // Download a file with client's API key. 307 | #[instrument(skip_all)] 308 | async fn download_file(&self, token: String, url: Url, destination: PathBuf) -> Result<()> { 309 | if destination.exists() { 310 | return Ok(()); 311 | } 312 | let client = octocrab::Octocrab::builder() 313 | .personal_token(token) 314 | .build()?; 315 | let res = client._get(url.to_string()).await?; 316 | tokio::fs::create_dir_all(destination.parent().unwrap()).await?; 317 | let mut out = tokio::fs::File::create(destination).await?; 318 | 319 | // Pulled some of this from octocrab itself. 320 | let body_stream = http_body_util::BodyStream::new(res.into_body()) 321 | .try_filter_map(|frame| futures_util::future::ok(frame.into_data().ok())) 322 | .map_err(|x| std::io::Error::new(ErrorKind::Other, x)); 323 | let mut body_stream_reader = StreamReader::new(body_stream); 324 | tokio::io::copy(&mut body_stream_reader, &mut out).await?; 325 | Ok(()) 326 | } 327 | 328 | /// Download a single asset from github. 329 | #[instrument(skip_all, fields(asset=%asset.filename))] 330 | async fn download_asset( 331 | &self, 332 | token: String, 333 | repo: Repo, 334 | version: String, 335 | asset: &ProviderAsset, 336 | ) -> Result<()> { 337 | let dest = self 338 | .cache 339 | .build_asset_path(&repo, &version, &asset.filename); 340 | info!( 341 | "Downloading {} to {} ({})", 342 | &asset.filename, 343 | dest.display(), 344 | &asset.download_url 345 | ); 346 | 347 | self.download_file(token, asset.github_asset_url.clone(), dest) 348 | .await?; 349 | Ok(()) 350 | } 351 | 352 | // Download SHA256SUMS file from a release. Always stores as SHA256SUMS, regardless of whether 353 | // it contains the repo/version prefix on the asset name in github. 354 | #[instrument(skip_all, fields(release=%release.tag_name))] 355 | async fn download_release_sha256sums( 356 | &self, 357 | token: String, 358 | repo: &Repo, 359 | release: &Release, 360 | ) -> Result<()> { 361 | let checksum = release 362 | .assets 363 | .iter() 364 | .find(|a| a.name.ends_with("SHA256SUMS")) 365 | .ok_or_else(|| anyhow!("No sha256sums found"))?; 366 | 367 | let dest = self.cache.build_asset_path( 368 | repo, 369 | &release.tag_name.trim_start_matches('v').to_string(), 370 | "SHA256SUMS", 371 | ); 372 | 373 | self.download_file(token.clone(), checksum.browser_download_url.clone(), dest) 374 | .await?; 375 | 376 | Ok(()) 377 | } 378 | 379 | /// Load SHA256SUMS from disk. This supports reading a file named either: 380 | /// 1. SHA256SUMS (exact) 381 | /// 2. terraform-provider-xxx_0.0.0_SHA256SUMS (where repo / semver match args values) 382 | #[instrument(skip_all)] 383 | pub async fn load_sha256sums(&self, repo: &Repo, release: &str) -> Result> { 384 | if let Ok(short_name) = self 385 | .cache 386 | .get_asset(repo, &release.to_owned(), "SHA256SUMS") 387 | .await 388 | { 389 | return Ok(short_name); 390 | } 391 | let long_name = format!( 392 | "{}_{}_SHA256SUMS", 393 | repo.name, 394 | release.trim_start_matches('v') 395 | ); 396 | self.cache 397 | .get_asset(repo, &release.to_owned(), &long_name) 398 | .await 399 | } 400 | 401 | /// Parse the SHA256SUMS file. It is expected to be in the standard text format as 402 | /// generated by running `sha256sums *` on the command line. 403 | #[instrument(skip_all)] 404 | pub async fn parse_checksums( 405 | &self, 406 | repo: &Repo, 407 | release: &Release, 408 | ) -> Result> { 409 | let res = self.load_sha256sums(repo, &release.tag_name).await?; 410 | 411 | Ok(String::from_utf8(res)? 412 | .lines() 413 | .filter_map(|x| x.split_once(" ")) 414 | .map(|(hash, filename)| (filename.to_string(), hash.to_string())) 415 | .collect()) 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use axum::{http::StatusCode, response::IntoResponse, routing::get, Json, Router}; 5 | use oauth::Authenticate; 6 | use serde_json::json; 7 | 8 | pub mod app_config; 9 | mod app_tracing; 10 | mod auth; 11 | mod download; 12 | pub mod github; 13 | mod middleware; 14 | pub mod oauth; 15 | mod provider_registry; 16 | mod signature; 17 | 18 | pub const PROVIDER_ROUTE_PREFIX: &str = "/providers/v1/"; 19 | pub const AUTH_ROUTE_PREFIX: &str = "/oauth"; 20 | pub const DOWNLOAD_ROUTE_PREFIX: &str = "/downloads/"; 21 | pub const SERVICE_DISCOVERY_PATH: &str = "/.well-known/terraform.json"; 22 | 23 | pub async fn router( 24 | config: &app_config::AppConfig, 25 | authenticator: Arc, 26 | ) -> Result { 27 | app_tracing::init(config)?; 28 | 29 | let registry_router = provider_registry::router(config, authenticator.clone()).await?; 30 | let auth_router = auth::router(authenticator.clone())?; 31 | let download_router = download::router(config, DOWNLOAD_ROUTE_PREFIX, authenticator.clone()); 32 | 33 | Ok(Router::new() 34 | .route(SERVICE_DISCOVERY_PATH, get(service_discovery_handler)) 35 | .nest(PROVIDER_ROUTE_PREFIX, registry_router) 36 | .nest(AUTH_ROUTE_PREFIX, auth_router) 37 | .merge(download_router) 38 | .layer(app_tracing::opentelemetry_tracing_layer())) 39 | } 40 | 41 | // https://www.terraform.io/internals/provider-registry-protocol#service-discovery 42 | pub async fn service_discovery_handler() -> impl IntoResponse { 43 | ( 44 | StatusCode::OK, 45 | Json(json!({ 46 | "providers.v1": PROVIDER_ROUTE_PREFIX, 47 | "login.v1": { 48 | "client": "terraform-cli", 49 | "grant_types": ["authz_code"], 50 | "authz": format!("{}/{}", AUTH_ROUTE_PREFIX, "authz"), 51 | "token": format!("{}/{}", AUTH_ROUTE_PREFIX, "token"), 52 | "ports": [10009, 10010], 53 | } 54 | })), 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use tfreg::oauth::GithubAuthenticator; 5 | use tracing::info; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<()> { 9 | let config = tfreg::app_config::load()?; 10 | 11 | let authenticator = Arc::new(GithubAuthenticator::new(&config)?); 12 | let app = tfreg::router(&config, authenticator).await?; 13 | 14 | let listener = tokio::net::TcpListener::bind(&config.addr).await.unwrap(); 15 | info!("listening on {}", config.addr); 16 | 17 | axum::serve(listener, app).await?; 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /src/middleware.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use axum::{ 4 | extract::Request, 5 | http::{self, StatusCode}, 6 | Extension, 7 | }; 8 | use serde::Deserialize; 9 | use tracing::{error, info, instrument}; 10 | 11 | use crate::{github::Repo, oauth::Authenticate}; 12 | 13 | // Determine whether the passed API key in the authorization header has read access to the github 14 | // repository indicated by the first two segments of the URL path (e.g. 15 | // /mattclement/example/foo/bar checks against github.com/mattclement/example). 16 | pub async fn header_auth( 17 | Extension(authenticator): Extension>, 18 | mut req: Request, 19 | next: axum::middleware::Next, 20 | ) -> Result { 21 | let token = req 22 | .headers() 23 | .get(http::header::AUTHORIZATION) 24 | .and_then(|header| header.to_str().ok()) 25 | .ok_or(StatusCode::UNAUTHORIZED)? 26 | .trim_start_matches("Bearer ") 27 | .to_string(); 28 | 29 | let repo = repo_from_path(req.uri().path()).ok_or(StatusCode::BAD_REQUEST)?; 30 | 31 | match authenticator.decrypt_token(token) { 32 | Ok(s) => { 33 | check_repo_permissions(s.clone(), &repo).await?; 34 | req.extensions_mut().insert(s); 35 | } 36 | Err(e) => { 37 | error!("{}", e); 38 | return Err(StatusCode::BAD_REQUEST); 39 | } 40 | } 41 | 42 | Ok(next.run(req).await) 43 | } 44 | 45 | #[derive(Debug, Deserialize)] 46 | struct TokenQS { 47 | token: String, 48 | } 49 | 50 | // query param auth values are only valid for a single use. 51 | pub async fn query_param_auth( 52 | Extension(authenticator): Extension>, 53 | req: Request, 54 | next: axum::middleware::Next, 55 | ) -> Result { 56 | let qs = req.uri().query().ok_or(StatusCode::UNAUTHORIZED)?; 57 | let token = serde_urlencoded::from_str::(qs) 58 | .map_err(|e| { 59 | error!("{}", e); 60 | StatusCode::UNAUTHORIZED 61 | })? 62 | .token; 63 | 64 | let repo = repo_from_path(req.uri().path()).ok_or(StatusCode::BAD_REQUEST)?; 65 | 66 | match authenticator 67 | .verify_single_use_token(token, req.uri().path().to_string()) 68 | .await 69 | { 70 | Ok(t) => { 71 | check_repo_permissions(t.token.clone(), &repo).await?; 72 | } 73 | Err(e) => { 74 | error!("{}", e); 75 | return Err(StatusCode::BAD_REQUEST); 76 | } 77 | } 78 | 79 | Ok(next.run(req).await) 80 | } 81 | 82 | #[instrument(skip_all)] 83 | async fn check_repo_permissions(token: String, repo: &Repo) -> Result<(), StatusCode> { 84 | octocrab::Octocrab::builder() 85 | .personal_token(token) 86 | .build() 87 | .map_err(|_| StatusCode::NOT_FOUND)? 88 | .repos(&repo.org, &repo.name) 89 | .get() 90 | .await 91 | .map_err(|e| { 92 | error!( 93 | "Error during repo {}/{} permission check: {}", 94 | repo.org, repo.name, e 95 | ); 96 | StatusCode::NOT_FOUND 97 | })?; 98 | 99 | info!("Token has access to {}/{}", repo.org, repo.name); 100 | Ok(()) 101 | } 102 | 103 | /// Extract the repo specified in the given URL path. This is designed to handle paths that point 104 | /// at either the downloads API or the provider API. 105 | fn repo_from_path(path: &str) -> Option { 106 | let repo_components_in_url_path = path 107 | .trim_start_matches('/') 108 | .trim_start_matches("downloads/") 109 | .splitn(3, '/') 110 | .take(2) 111 | .collect::>(); 112 | if repo_components_in_url_path.len() != 2 { 113 | return None; 114 | } 115 | Some(Repo::new( 116 | repo_components_in_url_path.first()?.to_string(), 117 | repo_components_in_url_path.last()?.to_string(), 118 | )) 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use super::*; 124 | 125 | #[test] 126 | fn test_repo_from_path() { 127 | let expected = Repo::new("org".to_string(), "name".to_string()); 128 | 129 | assert_eq!( 130 | expected, 131 | repo_from_path("/downloads/org/terraform-provider-name/2.3.4/SHA256SUMS").unwrap() 132 | ); 133 | 134 | assert_eq!( 135 | expected, 136 | repo_from_path("/org/terraform-provider-name/2.3.4/SHA256SUMS").unwrap() 137 | ) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/oauth/github.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | collections::{HashMap, HashSet}, 4 | sync::Arc, 5 | time::Duration, 6 | }; 7 | 8 | use async_trait::async_trait; 9 | use oauth2::{ 10 | AuthorizationCode, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, TokenResponse, 11 | }; 12 | use orion::aead; 13 | use tokio::sync::RwLock; 14 | 15 | use super::types::*; 16 | use super::*; 17 | use crate::app_config::AppConfig; 18 | 19 | const AUTH_URL: &str = "https://github.com/login/oauth/authorize"; 20 | const TOKEN_URL: &str = "https://github.com/login/oauth/access_token"; 21 | 22 | type Result = std::result::Result; 23 | 24 | /// Responsible for managing authentication. 25 | #[derive(Debug, Clone)] 26 | pub struct GithubAuthenticator { 27 | /// Exchanges code for token 28 | oauth2_client: oauth2::basic::BasicClient, 29 | 30 | /// Used for symmetric encryption of tokens. 31 | secret_key: Arc, 32 | 33 | /// Set of active PKCE challenges. 34 | pkce_challenges: Arc>>, 35 | 36 | /// Active single use tokens. Keys are base64url encoded random bytes. 37 | download_tokens: Arc>>, 38 | } 39 | 40 | impl GithubAuthenticator { 41 | /// Create a new authenticator for the given app configuration. 42 | pub fn new(config: &AppConfig) -> Result { 43 | let oauth2_client = utils::build_oauth2_client_from_config(config, AUTH_URL, TOKEN_URL)?; 44 | let secret_key = utils::build_secret_key(&config.secret_key)?; 45 | Ok(Self { 46 | oauth2_client, 47 | pkce_challenges: Arc::new(RwLock::new(HashSet::new())), 48 | download_tokens: Arc::new(RwLock::new(HashMap::new())), 49 | secret_key, 50 | }) 51 | } 52 | } 53 | 54 | #[async_trait] 55 | impl Authenticate for GithubAuthenticator { 56 | /// Begin an OAuth2 Authorization Code flow with PKCE. Returns the URL to redirect the 57 | /// user-agent to, where they can provide consent. 58 | async fn start_auth_flow(&self, auth_params: AuthFlow) -> Result { 59 | auth_params.validate()?; 60 | let redirect_uri = auth_params.redirect_uri.clone(); 61 | self.save_pkce_challenge(auth_params.code_challenge).await; 62 | self.auth_url_for_session(auth_params.state, redirect_uri) 63 | } 64 | 65 | /// Verify the PKCE challenge and exchange the code from the end user for a Github token. 66 | /// Returns a symmetrically encrypted token so the user can't use it directly, and we don't have 67 | /// rely on an external storage system for tokens. 68 | async fn exchange_code_for_token(&self, request: TokenRequest) -> Result { 69 | self.verify_pkce_challenge(request.code_verifier).await?; 70 | 71 | match self 72 | .oauth2_client 73 | .exchange_code(AuthorizationCode::new(request.code)) 74 | .request_async(oauth2::reqwest::async_http_client) 75 | .await 76 | { 77 | Ok(t) => Ok(self.encrypt_token(t.access_token().secret().to_string())?), 78 | Err(e) => Err(OAuth2Error::TokenExchange(e.to_string())), 79 | } 80 | } 81 | 82 | /// Encrypt and base64url encode the token. 83 | fn encrypt_token(&self, token: String) -> Result { 84 | aead::seal(&self.secret_key, &token.into_bytes()) 85 | .map_err(OAuth2Error::Encryption) 86 | .map(utils::base64url_encode) 87 | } 88 | 89 | /// Base64url decode and decrypt the token. 90 | fn decrypt_token(&self, ciphertext: String) -> Result { 91 | let bytes = utils::base64url_decode(ciphertext) 92 | .map_err(|e| OAuth2Error::Decryption(e.to_string()))?; 93 | 94 | let decrypted_bytes = aead::open(&self.secret_key, &bytes) 95 | .map_err(|e| OAuth2Error::Decryption(e.to_string()))?; 96 | 97 | String::from_utf8(decrypted_bytes).map_err(|e| OAuth2Error::Decryption(e.to_string())) 98 | } 99 | 100 | /// Generate a short-lived (default 60s) single-use token that can be used for a single URL. 101 | async fn generate_single_use_token( 102 | &self, 103 | token: String, 104 | url: String, 105 | duration: Option, 106 | ) -> Result { 107 | // generate some random bytes to use as the key we send the user. This keeps the size down 108 | // a bit. If we want to support horizontal scaling of this server (lol) we will have to 109 | // write the actual token out so any other instance that has the secret key can use the 110 | // token. 111 | let key_bytes = utils::generate_random_key()?; 112 | 113 | let key = utils::base64url_encode(key_bytes); 114 | let expires_at = 115 | utils::current_epoch() + duration.unwrap_or(Duration::from_secs(60)).as_secs(); 116 | let data = SingleUseToken { 117 | token, 118 | url, 119 | expires_at, 120 | }; 121 | 122 | if self 123 | .download_tokens 124 | .write() 125 | .await 126 | .insert(key.clone(), data) 127 | .is_some() 128 | { 129 | panic!("A randomly generated 32 byte key has collided. Go buy a lottery ticket."); 130 | } 131 | 132 | Ok(key) 133 | } 134 | 135 | /// Verify whether a single use token is valid for the given URL. This will consume the token's 136 | /// single possible use. 137 | async fn verify_single_use_token(&self, token: String, url: String) -> Result { 138 | let token = self 139 | .download_tokens 140 | .write() 141 | .await 142 | .remove(&token) 143 | .ok_or_else(|| OAuth2Error::SingleUseToken("Token does not exist".to_string()))?; 144 | 145 | if utils::current_epoch() >= token.expires_at { 146 | return Err(OAuth2Error::SingleUseToken("Token is expired".to_string())); 147 | } 148 | 149 | // The binary download urls get /downloads/ prefixed on them, but the signature/sha256sum 150 | // urls do not. This seems to be because req.uri().path() is not showing the full url when 151 | // an axum router is nested under a path. 152 | if !token.url.ends_with(&url) { 153 | return Err(OAuth2Error::SingleUseToken( 154 | "Invalid URL for token".to_string(), 155 | )); 156 | } 157 | 158 | Ok(token) 159 | } 160 | 161 | // Save the PKCE challenge. We will verify this later. 162 | async fn save_pkce_challenge(&self, challenge: String) { 163 | self.pkce_challenges.write().await.insert(challenge); 164 | } 165 | 166 | // Verify that sha256(code_verifier) exists in recorded challenges. It is immediately removed if 167 | // found. 168 | async fn verify_pkce_challenge(&self, code_verifier: String) -> Result<()> { 169 | let v = PkceCodeVerifier::new(code_verifier); 170 | let c = PkceCodeChallenge::from_code_verifier_sha256(&v); 171 | 172 | if !self.pkce_challenges.write().await.remove(c.as_str()) { 173 | return Err(OAuth2Error::PkceValidation( 174 | "Failed code verifier challenge".to_string(), 175 | )); 176 | } 177 | 178 | Ok(()) 179 | } 180 | 181 | // Generate an authentication URL for the user-agent to give consent at for the scopes we're 182 | // requesting here. We send the session_id so that we can look up the correct session on 183 | // callback. 184 | fn auth_url_for_session(&self, token: String, redirect_url: String) -> Result { 185 | let redirect_url = 186 | RedirectUrl::new(redirect_url).map_err(|e| OAuth2Error::AuthFlow(e.to_string()))?; 187 | 188 | Ok(self 189 | .oauth2_client 190 | .authorize_url(|| CsrfToken::new(token)) 191 | .set_redirect_uri(Cow::Owned(redirect_url)) 192 | .add_scope(oauth2::Scope::new("repo".to_string())) 193 | .url() 194 | .0 195 | .to_string()) 196 | } 197 | } 198 | 199 | #[cfg(test)] 200 | mod tests { 201 | use crate::app_config::LogFormat; 202 | 203 | use super::*; 204 | 205 | fn build_test_authenticator() -> GithubAuthenticator { 206 | GithubAuthenticator::new(&AppConfig { 207 | client_id: "a".into(), 208 | client_secret: "b".into(), 209 | addr: "127.0.0.1:50000".parse().unwrap(), 210 | log_level: "no".into(), 211 | cache_dir: "./no".into(), 212 | secret_key: "11111111111111111111111111111111".into(), 213 | otlp_endpoint: None, 214 | log_format: LogFormat::Json, 215 | otlp_headers: None, 216 | }) 217 | .unwrap() 218 | } 219 | 220 | #[test] 221 | fn encrypt_decrypt() { 222 | let auth = build_test_authenticator(); 223 | let plaintext = "test".to_string(); 224 | 225 | let ciphertext = auth.encrypt_token(plaintext.clone()).unwrap(); 226 | assert_ne!(plaintext, ciphertext); 227 | 228 | let decrypted = auth.decrypt_token(ciphertext).unwrap(); 229 | assert_eq!(decrypted, plaintext); 230 | } 231 | 232 | #[tokio::test] 233 | async fn auth_flow_handles_pkce_challenge() { 234 | let auth = build_test_authenticator(); 235 | let p = AuthFlow { 236 | code_challenge: "asdf".to_string(), 237 | code_challenge_method: "S256".to_string(), 238 | redirect_uri: "localhost:1234".to_string(), 239 | state: "random".to_string(), 240 | }; 241 | 242 | let url = auth.start_auth_flow(p).await; 243 | assert!(url.is_ok()); 244 | assert!(auth.pkce_challenges.read().await.len() == 1); 245 | } 246 | 247 | #[tokio::test] 248 | async fn exchange_code_for_token_checks_pkce_challenge() { 249 | let auth = build_test_authenticator(); 250 | auth.save_pkce_challenge("challenge".into()).await; 251 | assert!(auth.pkce_challenges.read().await.len() == 1); 252 | 253 | let res = auth 254 | .exchange_code_for_token(TokenRequest { 255 | code: "1234".to_string(), 256 | code_verifier: "this.needs.to.be.at.least.43.characters.long".to_string(), 257 | }) 258 | .await; 259 | 260 | assert!(matches!(res, Err(OAuth2Error::PkceValidation(_)))); 261 | assert!(auth.pkce_challenges.read().await.len() == 1); 262 | } 263 | 264 | #[tokio::test] 265 | async fn successful_pkce_validation_removes_from_set() { 266 | let auth = build_test_authenticator(); 267 | let verifier = "this.needs.to.be.at.least.43.characters.long".to_string(); 268 | let v = PkceCodeVerifier::new(verifier.clone()); 269 | let hash = PkceCodeChallenge::from_code_verifier_sha256(&v); 270 | 271 | auth.save_pkce_challenge(hash.as_str().to_string()).await; 272 | assert!(auth.pkce_challenges.read().await.len() == 1); 273 | 274 | let verify_result = auth.verify_pkce_challenge(verifier).await; 275 | assert!(verify_result.is_ok()); 276 | assert!(auth.pkce_challenges.read().await.len() == 0); 277 | } 278 | 279 | #[tokio::test] 280 | async fn single_use_token_missing() { 281 | let auth = build_test_authenticator(); 282 | let token = auth 283 | .verify_single_use_token("token".to_string(), "url".to_string()) 284 | .await; 285 | assert!(token.unwrap_err().to_string().contains("does not exist")); 286 | } 287 | 288 | #[tokio::test] 289 | async fn single_use_token_expired() { 290 | let auth = build_test_authenticator(); 291 | let url = "http://things".to_string(); 292 | let gh_token = "token".to_string(); 293 | 294 | let encrypted_token = auth 295 | .generate_single_use_token(gh_token.clone(), url.clone(), Some(Duration::default())) 296 | .await; 297 | assert!(encrypted_token.is_ok()); 298 | assert!(auth.download_tokens.read().await.len() == 1); 299 | 300 | let token = auth 301 | .verify_single_use_token(encrypted_token.unwrap(), url) 302 | .await; 303 | assert!(token.unwrap_err().to_string().contains("expired")); 304 | assert!(auth.download_tokens.read().await.len() == 0); 305 | } 306 | 307 | #[tokio::test] 308 | async fn single_use_token_bad_url() { 309 | let auth = build_test_authenticator(); 310 | let url = "http://things".to_string(); 311 | let gh_token = "token".to_string(); 312 | 313 | let encrypted_token = auth 314 | .generate_single_use_token(gh_token.clone(), url.clone(), None) 315 | .await; 316 | assert!(encrypted_token.is_ok()); 317 | assert!(auth.download_tokens.read().await.len() == 1); 318 | 319 | let token = auth 320 | .verify_single_use_token(encrypted_token.unwrap(), "bad url".to_string()) 321 | .await; 322 | assert!(token.unwrap_err().to_string().contains("Invalid URL")); 323 | assert!(auth.download_tokens.read().await.len() == 0); 324 | } 325 | 326 | #[tokio::test] 327 | async fn single_use_token_valid_returns_token() { 328 | let auth = build_test_authenticator(); 329 | let url = "http://things".to_string(); 330 | let gh_token = "token".to_string(); 331 | 332 | let encrypted_token = auth 333 | .generate_single_use_token(gh_token.clone(), url.clone(), None) 334 | .await; 335 | assert!(encrypted_token.is_ok()); 336 | assert!(auth.download_tokens.read().await.len() == 1); 337 | 338 | let token = auth 339 | .verify_single_use_token(encrypted_token.unwrap(), url) 340 | .await 341 | .unwrap(); 342 | assert!(auth.download_tokens.read().await.len() == 0); 343 | 344 | assert!(token.token == gh_token); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/oauth/mod.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | pub mod github; 4 | use async_trait::async_trait; 5 | pub use github::*; 6 | pub mod types; 7 | pub use types::*; 8 | pub mod shared_pat; 9 | 10 | mod utils; 11 | 12 | pub type Result = std::result::Result; 13 | 14 | #[async_trait] 15 | pub trait Authenticate: Send + Sync + 'static { 16 | /// Begin an OAuth2 Authorization Code flow with PKCE. Returns the URL to redirect the 17 | /// user-agent to, where they can provide consent. 18 | async fn start_auth_flow(&self, auth_params: AuthFlow) -> Result; 19 | 20 | /// Verify the PKCE challenge and exchange the code from the end user for a Github token. 21 | /// Returns a symmetrically encrypted token so the user can't use it directly, and we don't have 22 | /// rely on an external storage system for tokens. 23 | async fn exchange_code_for_token(&self, request: TokenRequest) -> Result; 24 | 25 | /// Encrypt and base64url encode the token. 26 | fn encrypt_token(&self, token: String) -> Result; 27 | 28 | /// Base64url decode and decrypt the token. 29 | fn decrypt_token(&self, ciphertext: String) -> Result; 30 | 31 | /// Generate a short-lived (default 60s) single-use token that can be used for a single URL. 32 | async fn generate_single_use_token( 33 | &self, 34 | token: String, 35 | url: String, 36 | duration: Option, 37 | ) -> Result; 38 | 39 | /// Verify whether a single use token is valid for the given URL. This will consume the token's 40 | /// single possible use. 41 | async fn verify_single_use_token(&self, token: String, url: String) -> Result; 42 | 43 | // Save the PKCE challenge. We will verify this later. 44 | async fn save_pkce_challenge(&self, challenge: String); 45 | 46 | // Verify that sha256(code_verifier) exists in recorded challenges. It is immediately removed if 47 | // found. 48 | async fn verify_pkce_challenge(&self, code_verifier: String) -> Result<()>; 49 | 50 | // Generate an authentication URL for the user-agent to give consent at for the scopes we're 51 | // requesting here. We send the session_id so that we can look up the correct session on 52 | // callback. 53 | fn auth_url_for_session(&self, token: String, redirect_url: String) -> Result; 54 | } 55 | -------------------------------------------------------------------------------- /src/oauth/shared_pat.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use tracing::debug; 3 | 4 | use super::{Authenticate, Result}; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct SharedPATAuthenticator(String); 8 | 9 | impl SharedPATAuthenticator { 10 | pub fn new(_config: &crate::app_config::AppConfig) -> Self { 11 | Self(String::new()) 12 | } 13 | pub fn with_github_pat(pat: String) -> Self { 14 | Self(pat.clone()) 15 | } 16 | } 17 | 18 | #[async_trait] 19 | impl Authenticate for SharedPATAuthenticator { 20 | async fn start_auth_flow(&self, _auth_params: super::AuthFlow) -> Result { 21 | debug!("start auth flow"); 22 | Ok("".to_string()) 23 | } 24 | 25 | async fn exchange_code_for_token(&self, _request: super::TokenRequest) -> Result { 26 | debug!("exchange code for token"); 27 | Ok(self.0.clone()) 28 | } 29 | 30 | fn encrypt_token(&self, _token: String) -> Result { 31 | debug!("encrypt token"); 32 | Ok(self.0.clone()) 33 | } 34 | 35 | fn decrypt_token(&self, _ciphertext: String) -> Result { 36 | debug!("decrypt token"); 37 | Ok(self.0.clone()) 38 | } 39 | 40 | async fn generate_single_use_token( 41 | &self, 42 | _token: String, 43 | _url: String, 44 | _duration: Option, 45 | ) -> Result { 46 | debug!("generate single use token"); 47 | Ok(self.0.clone()) 48 | } 49 | 50 | async fn verify_single_use_token( 51 | &self, 52 | token: String, 53 | url: String, 54 | ) -> Result { 55 | debug!("verify single use token"); 56 | Ok(super::SingleUseToken { 57 | token, 58 | url, 59 | expires_at: u64::MAX, 60 | }) 61 | } 62 | 63 | async fn save_pkce_challenge(&self, _challenge: String) { 64 | debug!("save pkce challenge"); 65 | } 66 | 67 | async fn verify_pkce_challenge(&self, _code_verifier: String) -> Result<()> { 68 | debug!("verify pkce challenge"); 69 | Ok(()) 70 | } 71 | 72 | fn auth_url_for_session(&self, _token: String, _redirect_url: String) -> Result { 73 | debug!("auth url for session"); 74 | Ok("".to_string()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/oauth/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use thiserror::Error; 3 | 4 | /// Information that is required to construct a single use token. 5 | #[derive(Debug, Serialize, Deserialize)] 6 | pub struct SingleUseToken { 7 | pub token: String, 8 | pub url: String, 9 | pub expires_at: u64, 10 | } 11 | 12 | /// Information needed to verify a PKCE challenge and exchange for a token. 13 | #[derive(Debug, Deserialize)] 14 | pub struct TokenRequest { 15 | pub code: String, 16 | pub code_verifier: String, 17 | } 18 | 19 | /// This info is the required data to start an OAuth2 Authorization Code flow with PKCE. 20 | #[derive(Debug, Serialize, Deserialize, Default)] 21 | pub struct AuthFlow { 22 | /// A SHA256 hashed string from the client. This will be verified in the final step of the 23 | /// OAuth2 flow in order for the client to prove that they're the same client that started 24 | /// this flow. 25 | pub code_challenge: String, 26 | 27 | /// Indicates the method the client intends to use for PKCE. We only support S256 here. 28 | pub code_challenge_method: String, 29 | 30 | /// The URI where we will redirect the user-agent after authentication. 31 | pub redirect_uri: String, 32 | 33 | /// Opaque data from the user, used for validation. The client expects this parameter to be 34 | /// returned from github, so that it knows we are the ones requesting access. 35 | pub state: String, 36 | } 37 | 38 | impl AuthFlow { 39 | /// Validate the auth flow parameters. We require a SHA256 PKCE code challenge. 40 | pub fn validate(&self) -> Result<(), OAuth2Error> { 41 | if self.code_challenge.is_empty() { 42 | return Err(OAuth2Error::PkceValidation( 43 | "No code challenge presented".to_string(), 44 | )); 45 | } 46 | if self.code_challenge_method != "S256" { 47 | return Err(OAuth2Error::PkceValidation( 48 | "Only S256 challenge method is allowed".to_string(), 49 | )); 50 | } 51 | Ok(()) 52 | } 53 | } 54 | 55 | #[derive(Debug, Error, Clone)] 56 | pub enum OAuth2Error { 57 | /// Configuration error of the OAuth2 client. 58 | #[error("Error while configuring oauth2 client: {0}")] 59 | ClientConfig(#[from] oauth2::url::ParseError), 60 | 61 | /// Error during the auth flow 62 | #[error("OAuth2 flow error: {0}")] 63 | AuthFlow(String), 64 | 65 | /// Error validating the PKCE code. 66 | #[error("PKCE error: {0}")] 67 | PkceValidation(String), 68 | 69 | /// An error occuring while exchanging a code from the user for a token from GitHub. 70 | #[error("Error exchanging code for token: {0}")] 71 | TokenExchange(String), 72 | 73 | /// Error with the encryption of the token. 74 | #[error("Token encryption error: {0}")] 75 | Encryption(#[from] orion::errors::UnknownCryptoError), 76 | 77 | /// Error with the decryption of the token. 78 | #[error("Token decryption error: {0}")] 79 | Decryption(String), 80 | 81 | /// Error with a single use token. 82 | #[error("Single use token error: {0}")] 83 | SingleUseToken(String), 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::*; 89 | 90 | #[test] 91 | fn validate_challenge_presence() { 92 | let p = AuthFlow::default(); 93 | let res = p.validate(); 94 | assert!(res.is_err()); 95 | } 96 | 97 | #[test] 98 | fn validate_challenge_method() { 99 | let p = AuthFlow { 100 | code_challenge: "not_empty".to_string(), 101 | ..Default::default() 102 | }; 103 | assert!(p.validate().is_err()); 104 | } 105 | 106 | #[test] 107 | fn validate_with_all_required_params() { 108 | let p = AuthFlow { 109 | code_challenge: "not_empty".to_string(), 110 | code_challenge_method: "S256".to_string(), 111 | ..Default::default() 112 | }; 113 | assert!(p.validate().is_ok()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/oauth/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | sync::Arc, 3 | time::{SystemTime, UNIX_EPOCH}, 4 | }; 5 | 6 | use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, TokenUrl}; 7 | use orion::aead; 8 | 9 | use crate::app_config::AppConfig; 10 | use base64::{engine::general_purpose, Engine as _}; 11 | 12 | use super::{OAuth2Error, Result}; 13 | 14 | pub fn base64url_encode>(key_bytes: T) -> String { 15 | general_purpose::URL_SAFE.encode(key_bytes) 16 | } 17 | 18 | pub fn base64url_decode>( 19 | key_bytes: T, 20 | ) -> std::result::Result, base64::DecodeError> { 21 | general_purpose::URL_SAFE.decode(key_bytes) 22 | } 23 | 24 | pub fn build_secret_key(secret_key: &str) -> Result> { 25 | aead::SecretKey::from_slice(secret_key.as_bytes()) 26 | .map(Arc::new) 27 | .map_err(OAuth2Error::Encryption) 28 | } 29 | 30 | pub fn build_oauth2_client_from_config( 31 | config: &AppConfig, 32 | auth_url: &str, 33 | token_url: &str, 34 | ) -> Result { 35 | Ok(BasicClient::new( 36 | ClientId::new(config.client_id.clone()), 37 | Some(ClientSecret::new(config.client_secret.clone())), 38 | AuthUrl::new(auth_url.to_string())?, 39 | Some(TokenUrl::new(token_url.to_string())?), 40 | )) 41 | } 42 | 43 | pub fn current_epoch() -> u64 { 44 | SystemTime::now() 45 | .duration_since(UNIX_EPOCH) 46 | .unwrap() 47 | .as_secs() 48 | } 49 | 50 | pub fn generate_random_key() -> Result<[u8; 64]> { 51 | let mut key_bytes = [0u8; 64]; 52 | orion::util::secure_rand_bytes(&mut key_bytes).map_err(OAuth2Error::Encryption)?; 53 | Ok(key_bytes) 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::*; 59 | 60 | #[test] 61 | fn base64() { 62 | let key = generate_random_key().unwrap(); 63 | let encoded = base64url_encode(key); 64 | let decoded = base64url_decode(encoded).unwrap(); 65 | assert_eq!(key.to_vec(), decoded); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/provider_registry.rs: -------------------------------------------------------------------------------- 1 | use crate::{middleware, oauth::Authenticate}; 2 | use anyhow::Result; 3 | use axum::{ 4 | extract::Path, 5 | http::{self, StatusCode}, 6 | routing::get, 7 | Extension, Json, Router, 8 | }; 9 | use serde::{Deserialize, Serialize}; 10 | use serde_json::{json, Value}; 11 | use std::sync::Arc; 12 | use tracing::{error, instrument}; 13 | 14 | use crate::{ 15 | github::{Download, GPGKey}, 16 | signature::GPGSigner, 17 | }; 18 | 19 | pub async fn router( 20 | config: &crate::app_config::AppConfig, 21 | auth: Arc, 22 | ) -> Result { 23 | let signer = GPGSigner::new()?; 24 | let client = crate::github::Client::new( 25 | config.cache_dir.clone(), 26 | GPGKey::new(signer.fingerprint(), signer.ascii_armor()?), 27 | ) 28 | .await?; 29 | 30 | let query_middleware = axum::middleware::from_fn(middleware::query_param_auth::); 31 | let header_middleware = axum::middleware::from_fn(middleware::header_auth::); 32 | 33 | let sig_router = Router::new() 34 | .route("/:namespace/:provider/:version/signature", Signature::get()) 35 | .route_layer(query_middleware); 36 | 37 | let provider_router = Router::new() 38 | .route("/:namespace/:provider/versions", ListVersions::get()) 39 | .route( 40 | "/:namespace/:provider/:version/download/:os/:arch", 41 | FindPackage::get::(), 42 | ) 43 | .route_layer(header_middleware); 44 | 45 | let r = Router::new() 46 | .merge(sig_router) 47 | .merge(provider_router) 48 | .layer(Extension(Arc::new(signer))) 49 | .layer(Extension(Arc::new(client))) 50 | .layer(Extension(auth)); 51 | 52 | Ok(r) 53 | } 54 | 55 | // https://www.terraform.io/internals/provider-registry-protocol#list-available-versions 56 | #[derive(Deserialize)] 57 | struct ListVersions { 58 | namespace: String, 59 | provider: String, 60 | } 61 | 62 | impl ListVersions { 63 | pub fn get() -> axum::routing::MethodRouter { 64 | get(Self::handler) 65 | } 66 | 67 | #[instrument(skip_all, name = "list_versions")] 68 | async fn handler( 69 | Path(params): Path, 70 | Extension(token): Extension, 71 | Extension(client): Extension>, 72 | ) -> Result, http::StatusCode> { 73 | match client 74 | .get_versions(token, params.namespace, params.provider) 75 | .await 76 | { 77 | Some(r) => Ok(Json(json!({ "versions": r }))), 78 | None => Err(StatusCode::NOT_FOUND), 79 | } 80 | } 81 | } 82 | 83 | // https://www.terraform.io/internals/provider-registry-protocol#find-a-provider-package 84 | #[derive(Serialize, Deserialize)] 85 | pub struct FindPackage { 86 | pub namespace: String, 87 | pub provider: String, 88 | pub version: String, 89 | pub os: String, 90 | pub arch: String, 91 | } 92 | 93 | impl FindPackage { 94 | pub fn get() -> axum::routing::MethodRouter { 95 | get(Self::handler::) 96 | } 97 | 98 | #[instrument(skip_all, name = "find_package")] 99 | async fn handler( 100 | Path(params): Path, 101 | Extension(token): Extension, 102 | Extension(authenticator): Extension>, 103 | Extension(client): Extension>, 104 | ) -> Result, http::StatusCode> { 105 | // generate new time limited, single use session based off of this one that will 106 | // be added as a query parameter to the download url here. 107 | 108 | let mut download = client 109 | .get_provider_info( 110 | token.clone(), 111 | params.namespace, 112 | params.provider, 113 | params.version, 114 | params.os, 115 | params.arch, 116 | ) 117 | .await 118 | .ok_or(StatusCode::NOT_FOUND)?; 119 | 120 | for url in [ 121 | &mut download.asset.download_url, 122 | &mut download.asset.shasums_url, 123 | &mut download.asset.shasums_signature_url, 124 | ] { 125 | let t = authenticator 126 | .generate_single_use_token(token.clone(), url.clone(), None) 127 | .await 128 | .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; 129 | 130 | let qs = serde_urlencoded::to_string(&[("token", t)]).map_err(|e| { 131 | error!("{}", e); 132 | StatusCode::INTERNAL_SERVER_ERROR 133 | })?; 134 | let qs = &format!("?{}", qs); 135 | url.push_str(qs); 136 | } 137 | Ok(Json(download)) 138 | } 139 | } 140 | 141 | // https://www.terraform.io/internals/provider-registry-protocol#find-a-provider-package 142 | #[derive(Deserialize)] 143 | struct Signature { 144 | namespace: String, 145 | provider: String, 146 | version: String, 147 | } 148 | 149 | impl Signature { 150 | pub fn get() -> axum::routing::MethodRouter { 151 | get(Self::handler) 152 | } 153 | 154 | #[instrument(skip_all, name = "signature")] 155 | async fn handler( 156 | Path(params): Path, 157 | Extension(signer): Extension>, 158 | Extension(client): Extension>, 159 | ) -> Result, http::StatusCode> { 160 | let repo = crate::github::Repo::new(params.namespace, params.provider); 161 | let checksums = client 162 | .load_sha256sums(&repo, ¶ms.version) 163 | .await 164 | .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; 165 | 166 | signer 167 | .sign(&checksums) 168 | .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/signature.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, time::Duration}; 2 | 3 | use anyhow::Result; 4 | use sequoia_openpgp::{ 5 | cert::{CertBuilder, CipherSuite}, 6 | policy::StandardPolicy, 7 | serialize::{ 8 | stream::{Message, Signer}, 9 | SerializeInto, 10 | }, 11 | }; 12 | use tracing::{debug, instrument}; 13 | 14 | pub struct GPGSigner { 15 | cert: sequoia_openpgp::Cert, 16 | } 17 | 18 | impl GPGSigner { 19 | #[instrument(name = "GPGSigner::new")] 20 | pub fn new() -> Result { 21 | debug!("Generating GPG signing key"); 22 | let (cert, _) = CertBuilder::general_purpose(CipherSuite::RSA3k, Some("tfreg@localhost")) 23 | .set_validity_period(Duration::from_secs(60 * 60 * 24 * 7 * 52)) 24 | .generate()?; 25 | Ok(Self { cert }) 26 | } 27 | 28 | pub fn fingerprint(&self) -> String { 29 | self.cert.fingerprint().to_hex() 30 | } 31 | 32 | pub fn ascii_armor(&self) -> Result { 33 | Ok(String::from_utf8(self.cert.armored().to_vec()?)?) 34 | } 35 | 36 | #[instrument(skip_all, name = "sign")] 37 | pub fn sign(&self, plaintext: &[u8]) -> Result> { 38 | let keypair = self 39 | .cert 40 | .keys() 41 | .unencrypted_secret() 42 | .with_policy(&StandardPolicy::new(), None) 43 | .supported() 44 | .alive() 45 | .revoked(false) 46 | .for_signing() 47 | .next() 48 | .unwrap() 49 | .key() 50 | .clone() 51 | .into_keypair()?; 52 | let mut buf = vec![]; 53 | let message = Message::new(&mut buf); 54 | let mut signer = Signer::new(message, keypair).detached().build()?; 55 | signer.write_all(plaintext)?; 56 | signer.finalize()?; 57 | Ok(buf) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | cortex = { 4 | source = "tfreg.mclement.dev/cortexapps/cortex" 5 | version = "0.3.1" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/routes.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use axum::{body::Body, http::Request, Router}; 3 | use http_body_util::BodyExt; 4 | use orion::hazardous::hash::sha2::sha256::Sha256; 5 | use serde_json::Value; 6 | use std::{collections::HashMap, sync::Arc}; 7 | use tfreg::{ 8 | app_config::{self, AppConfig}, 9 | oauth::shared_pat::SharedPATAuthenticator, 10 | SERVICE_DISCOVERY_PATH, 11 | }; 12 | use tower::ServiceExt; 13 | 14 | fn test_config() -> AppConfig { 15 | let mut f = app_config::load().unwrap(); 16 | f.log_level = "disabled".to_string(); 17 | 18 | f.validate().expect("Invalid config"); 19 | f 20 | } 21 | 22 | // Setting this environment variable and using the SharedPATAuthenticator will let us bypass all of 23 | // the oauth2 auth flow and send the shared personal access token instead of the end-user, which we 24 | // don't have in a test. 25 | fn get_shared_pat() -> Result { 26 | Ok(std::env::var("TFREG_TEST_PAT")?) 27 | } 28 | 29 | async fn app() -> Result { 30 | let c = test_config(); 31 | tfreg::router( 32 | &c, 33 | Arc::new(SharedPATAuthenticator::with_github_pat(get_shared_pat()?)), 34 | ) 35 | .await 36 | } 37 | 38 | #[tokio::test] 39 | async fn buildable() { 40 | assert!(app().await.is_ok()); 41 | } 42 | 43 | #[tokio::test] 44 | async fn service_discovery() -> Result<()> { 45 | let app = app().await?; 46 | let req = Request::builder() 47 | .uri(SERVICE_DISCOVERY_PATH) 48 | .body(Body::empty())?; 49 | 50 | let res = app.oneshot(req).await?; 51 | assert_eq!(res.status(), 200); 52 | 53 | let body = res.into_body().collect().await?.to_bytes(); 54 | let body: Value = serde_json::from_slice(&body)?; 55 | assert!(body.is_object()); 56 | 57 | let body = body.as_object().unwrap(); 58 | assert!(body 59 | .keys() 60 | .all(|k| ["providers.v1", "login.v1"].contains(&k.as_str()))); 61 | 62 | Ok(()) 63 | } 64 | 65 | #[tokio::test] 66 | async fn test_download_flow() -> Result<()> { 67 | let app = app().await?; 68 | let org = "cortexapps"; 69 | let provider = "cortex"; 70 | let namespace = format!("{org}/{provider}/"); 71 | let version_url = url::Url::parse("http://localhost")? 72 | .join(tfreg::PROVIDER_ROUTE_PREFIX)? 73 | .join(&namespace)? 74 | .join("versions")?; 75 | let version_req = Request::builder() 76 | .uri(version_url.as_str()) 77 | .header("Authorization", "Bearer foo") 78 | .body(Body::empty())?; 79 | 80 | // Get versions 81 | let version_res = app.clone().oneshot(version_req).await?; 82 | assert_eq!(version_res.status(), 200); 83 | 84 | let body = version_res.into_body().collect().await?.to_bytes(); 85 | let body: HashMap> = serde_json::from_slice(&body)?; 86 | 87 | assert!(body.keys().len() == 1); 88 | assert!(body.get("versions").is_some()); 89 | 90 | let version = body.get("versions").unwrap().iter().last().unwrap(); 91 | let platform = version.platforms.last().unwrap(); 92 | 93 | // Download the version info for one platform 94 | let download_info_url = url::Url::parse("http://localhost")? 95 | .join(tfreg::PROVIDER_ROUTE_PREFIX)? 96 | .join(&namespace)? 97 | .join(&format!( 98 | "{}/download/{}/{}", 99 | version.version, platform.os, platform.arch 100 | ))?; 101 | let download_info_req = Request::builder() 102 | .uri(download_info_url.as_str()) 103 | .header("Authorization", "Bearer foo") 104 | .body(Body::empty())?; 105 | let download_info_res = app.clone().oneshot(download_info_req).await?; 106 | assert_eq!(download_info_res.status(), 200); 107 | 108 | let body = download_info_res.into_body().collect().await?.to_bytes(); 109 | let body: tfreg::github::Download = serde_json::from_slice(&body)?; 110 | let checksum = hex::decode(body.asset.shasum)?; 111 | 112 | // download package 113 | let download_url = url::Url::parse("http://localhost")?.join(&body.asset.download_url)?; 114 | let download_req = Request::builder() 115 | .uri(download_url.as_str()) 116 | .body(Body::empty())?; 117 | 118 | let download_res = app.clone().oneshot(download_req).await?; 119 | assert!(download_res.status() == 200, "{}", download_res.status()); 120 | let body = download_res.into_body().collect().await?.to_bytes(); 121 | 122 | // Verify the downloaded body's sha256 digest matches what was reported earlier 123 | let digest = Sha256::digest(&body)?; 124 | assert!(digest.as_ref() == checksum); 125 | 126 | Ok(()) 127 | } 128 | --------------------------------------------------------------------------------