diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fff0f03 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,32 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.DS_Store +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/charts +**/docker-compose* +**/compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/secrets.dev.yaml +**/values.dev.yaml +/bin +/target +LICENSE +README.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a268b63 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0d2714d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,148 @@ +name: Release Operator + +on: + push: + branches: + - main + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as <account>/<repo> + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Get app version from chart + uses: mikefarah/yq@v4.35.1 + id: app_version + with: + cmd: yq '.appVersion' charts/bitwarden-secret-operator/Chart.yaml + + - id: repository + run: echo IMAGE_NAME=$(echo ${{ env.IMAGE_NAME }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@v3.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Check if app version was already built (and if so, skip further steps). + - name: Check for existing image + if: github.event_name != 'pull_request' + id: image_exists + continue-on-error: true + run: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.app_version.outputs.result }} + + - name: Setup Docker buildx + if: ${{ steps.image_exists.outcome != 'success' }} + uses: docker/setup-buildx-action@v3.2.0 + + - name: Extract Docker metadata + id: meta + if: ${{ steps.image_exists.outcome != 'success' }} + uses: docker/metadata-action@v5.5.1 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + if: ${{ steps.image_exists.outcome != 'success' }} + id: build-and-push + uses: docker/build-push-action@v5.3.0 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.app_version.outputs.result }},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Install cosign + if: ${{ steps.image_exists.outcome != 'success' && github.event_name != 'pull_request' }} + uses: sigstore/cosign-installer@v3.1.2 + + - name: Sign the published Docker image + if: ${{ steps.image_exists.outcome != 'success' && github.event_name != 'pull_request' }} + env: + COSIGN_EXPERIMENTAL: "true" + run: echo "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.app_version.outputs.result }}" | xargs -I {} cosign sign -y {}@${{ steps.build-and-push.outputs.digest }} + + release: + needs: build + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - id: repository + run: echo IMAGE_NAME=$(echo ${{ env.IMAGE_NAME }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: v3.10.0 + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.6.0 + with: + charts_dir: charts + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - name: Get app version from chart + uses: mikefarah/yq@v4.43.1 + id: app_version + with: + cmd: yq '.appVersion' charts/bitwarden-secret-operator/Chart.yaml + + - name: Create SBOM + uses: anchore/sbom-action@v0 + with: + image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.app_version.outputs.result }} + + - name: Publish SBOM + uses: anchore/sbom-action/publish-sbom@v0 + with: + sbom-artifact-match: ".*\\.spdx\\.json" + + - name: Get Latest Tag + id: previoustag + uses: WyriHaximus/github-action-get-previous-tag@v1 + + - name: Download SBOM from github action + uses: actions/download-artifact@v4 + with: + name: ${{ env.ANCHORE_SBOM_ACTION_PRIOR_ARTIFACT }} + + - name: Add SBOM to release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file_glob: true + file: olympusgg-bitwarden-secret-operator-rs_*.spdx.json + tag: ${{ steps.previoustag.outputs.tag }} + overwrite: true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..000bb2c --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d15248e --- /dev/null +++ b/.gitignore @@ -0,0 +1,105 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,jetbrains+all +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,jetbrains+all + +### JetBrains+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### JetBrains+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# End of https://www.toptal.com/developers/gitignore/api/rust,jetbrains+all diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..74505e4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2567 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "async-trait" +version = "0.1.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core 0.4.3", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.2.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.0", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bitwarden-operator-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum 0.7.5", + "chrono", + "eyre", + "futures", + "futures-util", + "k8s-openapi", + "kube", + "log", + "metrics", + "metrics-exporter-prometheus", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry_sdk", + "schemars", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tonic", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.4", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.55", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.28", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.28", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.2.0", + "pin-project-lite", + "socket2", + "tokio", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6" +dependencies = [ + "serde", + "serde_json", + "thiserror", + "treediff", +] + +[[package]] +name = "jsonpath-rust" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96acbc6188d3bd83519d053efec756aa4419de62ec47be7f28dec297f7dc9eb0" +dependencies = [ + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror", +] + +[[package]] +name = "k8s-openapi" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "550f99d93aa4c2b25de527bce492d772caf5e21d7ac9bd4b508ba781c8d91e30" +dependencies = [ + "base64 0.21.7", + "chrono", + "serde", + "serde-value", + "serde_json", +] + +[[package]] +name = "kube" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462fe330a0617b276ec864c2255810adcdf519ecb6844253c54074b2086a97bc" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe0d65dd6f3adba29cfb84f19dfe55449c7f6c35425f9d8294bec40313e0b64" +dependencies = [ + "base64 0.21.7", + "bytes", + "chrono", + "either", + "futures", + "home", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-rustls", + "hyper-timeout", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem", + "pin-project", + "rustls", + "rustls-pemfile", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6b42844e9172f631b8263ea9ce003b9251da13beb1401580937ad206dd82f4c" +dependencies = [ + "chrono", + "form_urlencoded", + "http 0.2.12", + "json-patch", + "k8s-openapi", + "once_cell", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-derive" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5b5a111ee287bd237b8190b8c39543ea9fd22f79e9c32a36c24e08234bcda22" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.55", +] + +[[package]] +name = "kube-runtime" +version = "0.88.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc06275064c81056fbb28ea876b3fb339d970e8132282119359afca0835c0ea" +dependencies = [ + "ahash", + "async-trait", + "backoff", + "derivative", + "futures", + "hashbrown 0.14.3", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "smallvec", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "metrics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be3cbd384d4e955b231c895ce10685e3d8260c5ccffae898c96c723b0772835" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d58e362dc7206e9456ddbcdbd53c71ba441020e62104703075a69151e38d85f" +dependencies = [ + "base64 0.22.0", + "indexmap 2.2.6", + "metrics", + "metrics-util", + "quanta", + "thiserror", +] + +[[package]] +name = "metrics-util" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b07a5eb561b8cbc16be2d216faf7757f9baf3bfb94dbb0fae3df8387a5bb47f" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.14.3", + "metrics", + "num_cpus", + "quanta", + "sketches-ddsketch", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "opentelemetry" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d57987be3f2aeb70d385fff9b27fb74c5723cc9a52d904d4f9c807a0667bf" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", + "urlencoding", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a016b8d9495c639af2145ac22387dcb88e44118e45320d9238fbf4e7889abcb" +dependencies = [ + "async-trait", + "futures-core", + "http 0.2.12", + "opentelemetry", + "opentelemetry-proto", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", + "prost", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8fddc9b68f5b80dae9d6f510b88e02396f006ad48cac349411fbecc80caae4" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ab5bd6c42fb9349dcf28af2ba9a0667f697f9bdcca045d39f2cec5543e2910" + +[[package]] +name = "opentelemetry_sdk" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e90c7113be649e31e9a0f8b5ee24ed7a16923b322c3c5ab6367469c049d6b7e" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "once_cell", + "opentelemetry", + "ordered-float 4.2.0", + "percent-encoding", + "rand", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-float" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "pest_meta" +version = "2.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "quanta" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "raw-cpuid" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.6", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "chrono", + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float 2.10.1", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.2.6", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "sketches-ddsketch" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384595c11a4e2969895cad5a8c4029115f5ab956a9e5ef4de79d11a426e5f20c" + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.6.20", + "base64 0.21.7", + "bytes", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "base64 0.21.7", + "bitflags 2.5.0", + "bytes", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "http-range-header", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9be14ba1bbe4ab79e9229f7f89fab8d120b865859f10527f31c033e599d2284" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "treediff" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d127780145176e2b5d16611cc25a900150e86e9fd79d3bde6ff3a37359c9cb5" +dependencies = [ + "serde_json", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.55", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..18bf23d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bitwarden-operator-rs" +version = "0.1.0" +edition = "2021" + + +[dependencies] +tokio = { version = "1.36", features = ["full", "macros", "rt-multi-thread"] } +serde = { version = "1.0", features = [] } +serde_json = { version = "1.0" } +kube = { version = "0.88", features = ["runtime", "derive", "client"] } +k8s-openapi = { version = "0.21", features = ["latest"] } +schemars = { version = "0.8", features = ["chrono"] } +anyhow = "1.0" +log = "0.4" +eyre = "0.6" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] } +chrono = { version = "0.4", features = ["serde"] } +thiserror = "1.0" +opentelemetry = { version = "0.22", features = ["trace", "default"] } +opentelemetry_sdk = { version = "0.22", features = ["trace", "rt-tokio"] } +opentelemetry-otlp = { version = "0.15.0", features = ["tokio", "grpc-tonic"] } +tracing-opentelemetry = { version = "0.23" } +tonic = "0.11" +futures = "0.3.30" +futures-util = "0.3.30" +serde_yaml = "0.9" +axum = "0.7" +metrics = { version = "0.22", default-features = false } +metrics-exporter-prometheus = { version = "0.14", default-features = false } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..59dcdf4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,65 @@ +# syntax=docker/dockerfile:1 + +ARG RUST_VERSION=1.77.0 +ARG APP_NAME=bitwarden-operator-rs + +################################################################################ +# Create a stage for building the application. + +FROM rust:${RUST_VERSION}-alpine AS build +ARG APP_NAME +WORKDIR /app + +# Install host build dependencies. +RUN apk add --no-cache clang lld musl-dev git + +# Build the application. +# Leverage a cache mount to /usr/local/cargo/registry/ +# for downloaded dependencies, a cache mount to /usr/local/cargo/git/db +# for git repository dependencies, and a cache mount to /app/target/ for +# compiled dependencies which will speed up subsequent builds. +# Leverage a bind mount to the src directory to avoid having to copy the +# source code into the container. Once built, copy the executable to an +# output directory before the cache mounted /app/target is unmounted. +RUN --mount=type=bind,source=src,target=src \ + --mount=type=bind,source=Cargo.toml,target=Cargo.toml \ + --mount=type=bind,source=Cargo.lock,target=Cargo.lock \ + --mount=type=cache,target=/app/target/ \ + --mount=type=cache,target=/usr/local/cargo/git/db \ + --mount=type=cache,target=/usr/local/cargo/registry/ \ +cargo build --locked --release && \ +cp ./target/release/$APP_NAME /bin/bitwarden-secret-operator-rs + +################################################################################ +# Create a new stage for running the application that contains the minimal +# runtime dependencies for the application. This often uses a different base +# image from the build stage where the necessary files are copied from the build +# stage. +# +# The example below uses the alpine image as the foundation for running the app. +# By specifying the "3.18" tag, it will use version 3.18 of alpine. If +# reproducability is important, consider using a digest +# (e.g., alpine@sha256:664888ac9cfd28068e062c991ebcff4b4c7307dc8dd4df9e728bedde5c449d91). +FROM alpine:3.18 AS final + +# Create a non-privileged user that the app will run under. +# See https://docs.docker.com/go/dockerfile-user-best-practices/ +ARG UID=10001 +RUN adduser \ + --disabled-password \ + --gecos "" \ + --home "/nonexistent" \ + --shell "/sbin/nologin" \ + --no-create-home \ + --uid "${UID}" \ + appuser +USER appuser + +# Copy the executable from the "build" stage. +COPY --from=build /bin/bitwarden-secret-operator-rs /bin/ + +# Expose the port that the application listens on. +EXPOSE 3001 + +# What the container should run when it is started. +CMD ["/bin/bitwarden-secret-operator-rs"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3db1904 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Olympus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8458df --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# bitwarden-secret-operator-rs + +bitwarden-secret-operator-rs is a kubernetes Operator written in Rust thanks +to [kube-rs](https://kube.rs). + +The goal is to create [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/) objects while using Bitwarden as the source of truth for your secret values. + +It currently is used in production by [OlympusGG](https://github.com/OlympusGG), for our GitOps +powered cluster management. + +<p align="center"> + <img src="logo.png" alt="bitwarden secret operator logo"/> +</p> + +> DISCLAIMER: +> This project wraps the BitWarden CLI as we didn't want to rewrite a client for BitWarden and BitWarden does not offer +> easy to use public client libraries +> +> If you need multi-line (SSH key, Certificate...) like we did, use secure note until BitWarden +> implements [Multiline support](https://community.bitwarden.com/t/add-an-additional-multi-line-text-field/2165) + +## Features + +- [x] Compatible with original [.NET Bitwarden Operator](https://github.com/OlympusGG/bitwarden-secret-operator) +- [x] Automatically refreshing secrets through `bw sync` +- [x] Supporting: fields/notes +- [x] [Prometheus](https://prometheus.io/) Metrics +- [x] [OpenTelemetry](https://opentelemetry.io/) Traces + +## TODOs +- [ ] Unit testing +- [ ] More metrics/observability + +## Getting started + +You will need a `ClientID` and `ClientSecret` ([where to get these](https://bitwarden.com/help/personal-api-key/)) as +well as your password. +Expose these to the operator as described in this example: + +```yaml +env: +- name: BW_HOST + value: "https://vaultwarden.yourdomain.ai" +- name: BW_CLIENTID + value: "user.your-client-id" +- name: BW_CLIENTSECRET + value: "yourClientSecret" +- name: BW_PASSWORD + value: "YourSuperSecurePassword" +- name: SECRET_REFRESH_RATE # optional, by default it's 15 seconds, this value is to define how frequently `bw sync` is called + value: "00:00:30" # TimeSpan (hh:mm:ss) +- name: OPENTELEMETRY_ENDPOINT_URL + value: "otel-collector.namespace.svc.cluster.local" +- name: METRICS_ENDPOINT + value: "127.0.0.1:3001" +``` + +the helm template will use all environment variables from this secret, so make sure to prepare this secret with the key +value pairs as described above. + +`BW_HOST` can be omitted if you are using the Bitwarden SaaS offering. + +After that it is a basic helm deployment: + +```bash +helm repo add bitwarden-operator https://blowaxd.github.io/bitwarden-secret-operator-rs +helm repo update +kubectl create namespace bw-operator +helm upgrade --install --namespace bw-operator -f values.yaml bw-operator bitwarden-operator/bitwarden-secret-operator-rs +``` + +## BitwardenSecret + +And you are set to create your first secret using this operator. For that you need to add a CRD Object like this to your cluster: + +```yaml +--- +apiVersion: bitwarden-secret-operator-rs.io/v1beta1 +kind: BitwardenSecret +metadata: + name: my-secret-from-bitwarden +spec: + name: "my-secret-from-spec" # optional, will use the same name as CRD if not specified + namespace: "my-namespace" # optional, will use the same namespace as CRD if not specified + labels: # optional set of labels + here-my-label-1: test + type: "kubernetes.io/tls" # optional, will use `Opaque` by default + bitwardenId: 00000000-0000-0000-0000-000000000000 # optional, this id applies to all elements without `bitwardenId` specified + content: # required, array of objects + - bitwardenId: d4ff5941-53a4-4622-9385-2fcf910ae7e7 # optional, can be specified for a specific secret + bitwardenSecretField: myBitwardenField # optional, mutually exclusive with `bitwardenSecretField` but acts as a second choice + bitwardenUseNote: false # optional, mutually exclusive and prioritized over `bitwardenSecretField` + kubernetesSecretKey: MY_KUBERNETES_SECRET_KEY # required + kubernetesSecretValue: value # optional, alternative to stringData + - bitwardenUseNote: true # boolean, exclusive and prioritized over `bitwardenSecretField` + kubernetesSecretKey: MY_KUBERNETES_SECRET_KEY # required + stringData: # optional, string data + test: hello-world +``` + +## Credits/Thanks + +- [Bitwarden](https://bitwarden.com/) for their product +- [kube-rs](https://kube.rs) For their work on `kube-rs` diff --git a/charts/bitwarden-secret-operator/.helmignore b/charts/bitwarden-secret-operator/.helmignore new file mode 100644 index 0000000..c51516c --- /dev/null +++ b/charts/bitwarden-secret-operator/.helmignore @@ -0,0 +1,24 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +myvalues.yaml \ No newline at end of file diff --git a/charts/bitwarden-secret-operator/Chart.yaml b/charts/bitwarden-secret-operator/Chart.yaml new file mode 100644 index 0000000..137ccb5 --- /dev/null +++ b/charts/bitwarden-secret-operator/Chart.yaml @@ -0,0 +1,29 @@ +apiVersion: v2 +name: bitwarden-secret-operator +description: Deploy the Bitwarden Secret Operator + +type: application + +version: "0.15.0" + +appVersion: "0.15.0" + +keywords: + - operator + - bitwarden + - vaultwarden + - secret-management + - gitops + +icon: https://blowaxd.github.io/bitwarden-secret-operator-rs/logo.png + +home: https://blowaxd.github.io/bitwarden-secret-operator-rs/ + +sources: + - https://github.com/BlowaXD/bitwarden-secret-operator-rs + +kubeVersion: '>= 1.23.0-0' + +maintainers: + - name: BlowaXD + email: blowa@olympusgg.com diff --git a/charts/bitwarden-secret-operator/crds/bitwarden-secret.yaml b/charts/bitwarden-secret-operator/crds/bitwarden-secret.yaml new file mode 100644 index 0000000..fe09566 --- /dev/null +++ b/charts/bitwarden-secret-operator/crds/bitwarden-secret.yaml @@ -0,0 +1,97 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: bitwardensecrets.bitwarden-secret-operator.io +spec: + group: bitwarden-secret-operator.io + names: + kind: BitwardenSecret + listKind: BitwardenSecretList + plural: bitwardensecrets + singular: bitwardensecret + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + required: + - spec + properties: + status: + nullable: true + properties: + checksum: + description: "For internal stuff" + type: string + last_updated: + description: "For operator internal stuff" + format: date-time + nullable: true + type: string + required: + - checksum + type: object + spec: + description: Specification of the kubernetes object. + properties: + name: + description: Name of the Kubernetes Secret, defaults to the same name of the CRD + nullable: true + type: string + namespace: + description: Namespace where the Kubernetes Secret will be placed, defaults to the same namespace of the CRD + nullable: true + type: string + type: + description: Type of secret to create, defaults to Opaque if not specified + nullable: true + type: string + bitwardenId: + description: Name of the Bitwarden Secret, optional and can be overriden by fields in `content.bitwardenId` + nullable: true + type: string + labels: + description: A set of labels to put to the secret resource + nullable: true + type: object + x-kubernetes-preserve-unknown-fields: true + content: + description: Content of secret + items: + properties: + bitwardenId: + description: Name of the Bitwarden `id` field + nullable: true + type: string + bitwardenSecretField: + description: Name of the Bitwarden `field` to use + nullable: true + type: string + bitwardenUseNote: + description: Tells whether or not to use `note` instead of `fields` + nullable: true + type: boolean + kubernetesSecretKey: + description: Name of the Kubernetes Secret key + type: string + kubernetesSecretValue: + description: Name of the Kubernetes Secret Value + nullable: true + type: string + required: + - kubernetesSecretKey + type: object + type: array + stringData: + description: A set of string data to put to the secret + nullable: true + type: object + x-kubernetes-preserve-unknown-fields: true + required: + - content + type: object + type: object + served: true + storage: true + subresources: + status: { } diff --git a/charts/bitwarden-secret-operator/templates/_helpers.tpl b/charts/bitwarden-secret-operator/templates/_helpers.tpl new file mode 100644 index 0000000..3f20474 --- /dev/null +++ b/charts/bitwarden-secret-operator/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "bitwarden-secret-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "bitwarden-secret-operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "bitwarden-secret-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "bitwarden-secret-operator.labels" -}} +helm.sh/chart: {{ include "bitwarden-secret-operator.chart" . }} +{{ include "bitwarden-secret-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "bitwarden-secret-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "bitwarden-secret-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "bitwarden-secret-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "bitwarden-secret-operator.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/bitwarden-secret-operator/templates/cluster-role-binding.yaml b/charts/bitwarden-secret-operator/templates/cluster-role-binding.yaml new file mode 100644 index 0000000..42a43c2 --- /dev/null +++ b/charts/bitwarden-secret-operator/templates/cluster-role-binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "bitwarden-secret-operator.serviceAccountName" . }}-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "bitwarden-secret-operator.serviceAccountName" . }}-role +subjects: +- kind: ServiceAccount + name: {{ include "bitwarden-secret-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} diff --git a/charts/bitwarden-secret-operator/templates/cluster-role.yaml b/charts/bitwarden-secret-operator/templates/cluster-role.yaml new file mode 100644 index 0000000..7b4500b --- /dev/null +++ b/charts/bitwarden-secret-operator/templates/cluster-role.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "bitwarden-secret-operator.serviceAccountName" . }}-role +rules: +- apiGroups: [ "bitwarden-secret-operator.io" ] + resources: [ "bitwardensecrets" ] + verbs: [ "*" ] +- apiGroups: [ "" ] + resources: [ "secrets" ] + verbs: [ "*" ] +- apiGroups: [ "" ] + resources: [ "namespaces" ] + verbs: [ "list", "watch", "get" ] +- apiGroups: [ "apps" ] + resources: [ "deployments" ] + verbs: [ "list", "get" ] +- apiGroups: ["apps"] + resources: ["deployments/status"] + verbs: ["get","patch","update"] +- apiGroups: [ "" ] + resources: [ "events" ] + verbs: [ "create", "list", "watch", "get", "update" ] +- apiGroups: [ "apiextensions.k8s.io" ] + resources: [ "customresourcedefinitions" ] + verbs: [ "list", "watch" ] +- apiGroups: [ "admissionregistration.k8s.io/v1" ] + resources: [ "validatingwebhookconfigurations", "mutatingwebhookconfigurations" ] + verbs: [ "create", "patch" ] +- apiGroups: [ "coordination.k8s.io" ] + resources: [ "leases" ] + verbs: [ "*" ] diff --git a/charts/bitwarden-secret-operator/templates/deployment.yaml b/charts/bitwarden-secret-operator/templates/deployment.yaml new file mode 100644 index 0000000..64a90da --- /dev/null +++ b/charts/bitwarden-secret-operator/templates/deployment.yaml @@ -0,0 +1,73 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "bitwarden-secret-operator.fullname" . }} + labels: + {{- include "bitwarden-secret-operator.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "bitwarden-secret-operator.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "bitwarden-secret-operator.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "bitwarden-secret-operator.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + {{- with .Values.env }} + {{- . | toYaml | trim | nindent 10 }} + {{- end }} + {{- if .Values.externalConfigSecret.enabled }} + envFrom: + - secretRef: + name: {{ .Values.externalConfigSecret.name }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.httpPort }} + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 30 + timeoutSeconds: 1 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + timeoutSeconds: 1 + resources: + {{- toYaml .Values.resources | nindent 12 }} + terminationGracePeriodSeconds: 10 + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/bitwarden-secret-operator/templates/service-account.yaml b/charts/bitwarden-secret-operator/templates/service-account.yaml new file mode 100644 index 0000000..2c263de --- /dev/null +++ b/charts/bitwarden-secret-operator/templates/service-account.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "bitwarden-secret-operator.serviceAccountName" . }} + labels: + {{- include "bitwarden-secret-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/bitwarden-secret-operator/values.yaml b/charts/bitwarden-secret-operator/values.yaml new file mode 100644 index 0000000..48c8489 --- /dev/null +++ b/charts/bitwarden-secret-operator/values.yaml @@ -0,0 +1,71 @@ +# Default values for bitwarden-secret-operator-rs. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: ghcr.io/blowaxd/bitwarden-secret-operator-rs + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + # tag: "0.1.0" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +httpPort: 5000 + +#env: +# - name: BW_HOST +# value: "define_it" +# - name: BW_CLIENTID +# value: "define_it" +# - name: BW_CLIENTSECRET +# value: "define_it" +# - name: BW_PASSWORD +# value: "define_id" + +externalConfigSecret: + enabled: false + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 64Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..2ce7505 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,7 @@ +services: + bitwarden-secret-operator-rs: + build: + context: . + target: final + ports: + - 3001:3001 diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..251de5c Binary files /dev/null and b/logo.png differ diff --git a/src/bitwarden_cli/mod.rs b/src/bitwarden_cli/mod.rs new file mode 100644 index 0000000..6136941 --- /dev/null +++ b/src/bitwarden_cli/mod.rs @@ -0,0 +1,262 @@ +use crate::bitwarden_cli::BitwardenError::MissingEnvVariable; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::env; +use std::sync::Arc; + +use thiserror::Error; +use tokio::sync::RwLock; +use tonic::async_trait; +use tracing::{error, info}; + +#[derive(Debug, Clone, Default)] +pub struct BitwardenCliWrapperStorage { + session_token: Option<String>, + + last_unlock: Option<DateTime<Utc>>, + last_sync: Option<DateTime<Utc>>, + + needs_relog: bool, +} + +#[derive(Debug, Clone)] +pub struct BitwardenCliClient { + client_id: String, + client_secret: String, + client_password: String, + + storage: Arc<RwLock<BitwardenCliWrapperStorage>>, +} + +#[derive(Error, Debug)] +pub enum BitwardenError { + #[error("missing env variable {0}")] + MissingEnvVariable(String), + #[error("bw login failed")] + LoginFailed(String), + #[error("`bw sync` failed")] + SyncFailed, + #[error("`bw sync` failed because session token was not initialized")] + SyncFailedTokenMissing, + #[error("`bw unlock` failed")] + UnlockFailed, + #[error("bw get item failed: {0}, not found")] + ItemNotFound(String), + #[error("bw get item failed: {0}, error: {1}")] + GetItemGenericFail(String, String), + #[error("bitwarden command: {0} failed")] + IoError(#[from] std::io::Error), +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BitwardenItem { + pub id: String, + pub note: Option<String>, + pub fields: Option<Vec<BitwardenItemField>>, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BitwardenItemField { + pub name: String, + pub value: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BitwardenGetItemResponse { + pub data: Option<BitwardenItem>, + pub success: bool, +} + +const BW_CLIENTID: &str = "BW_CLIENTID"; +const BW_CLIENTSECRET: &str = "BW_CLIENTSECRET"; +const BW_PASSWORD: &str = "BW_PASSWORD"; + +#[async_trait] +pub trait SecretStoreGetItem { + type Error; + async fn get_item(&mut self, item_id: String) -> Result<(), Self::Error>; +} + +#[async_trait] +pub trait SecretStoreSynchronize { + type Error; + async fn sync(&mut self) -> Result<(), Self::Error>; +} + +impl BitwardenCliClient { + pub fn from_env() -> eyre::Result<Self> { + Ok(BitwardenCliClient { + client_id: env::var(BW_CLIENTID) + .map_err(|_| MissingEnvVariable(BW_CLIENTID.to_string()))? + .to_string(), + client_secret: env::var(BW_CLIENTSECRET) + .map_err(|_| MissingEnvVariable(BW_CLIENTSECRET.to_string()))? + .to_string(), + client_password: env::var(BW_PASSWORD) + .map_err(|_| MissingEnvVariable(BW_PASSWORD.to_string()))? + .to_string(), + storage: Arc::new(RwLock::new(BitwardenCliWrapperStorage::default())), + }) + } + + pub async fn login(&self) -> eyre::Result<(), BitwardenError> { + let client_id = self.client_id.clone(); + let client_secret = self.client_secret.clone(); + + info!("`bw login`"); + let output = tokio::process::Command::new("bw") + .args(["login", "--apikey", "--nointeraction"]) + .env(BW_CLIENTID, client_id) + .env(BW_CLIENTSECRET, client_secret) + .output() + .await?; + + let exit_status = output.status.code().unwrap_or_default(); + match exit_status { + 0 => { + // success + info!("Successfully logged in"); + Ok(()) + } + 1 => { + // error code 1, handling "Already logged in" scenario + let stderr = String::from_utf8(output.stderr) + .map_err(|_| BitwardenError::LoginFailed("Couldn't get stderr".to_string()))?; + + if !stderr.starts_with("You are already logged in as") { + return Err(BitwardenError::LoginFailed( + "Login Error: CLI returned exitCode 1 but not 'already logged in'" + .to_string(), + )); + } + + Ok(()) + } + x => { + error!("Login Error: CLI returned unhandled exitCode: {}", x); + Err(BitwardenError::LoginFailed(format!( + "Login Error: CLI returned unhandled exitCode: {}", + x + ))) + } + } + } + + pub async fn unlock(&self) -> Result<(), BitwardenError> { + let client_id = self.client_id.clone(); + let client_secret = self.client_secret.clone(); + let client_password = self.client_password.clone(); + + info!("`bw unlock`"); + let cmd = tokio::process::Command::new("bw") + .args(["unlock", "--passwordenv", "BW_PASSWORD", "--nointeraction"]) + .env("BW_CLIENTID", client_id) + .env("BW_CLIENTSECRET", client_secret) + .env("BW_PASSWORD", client_password) + .output() + .await; + + match cmd { + Ok(output) => { + let output_str = String::from_utf8(output.stdout).unwrap(); + let session_text = "BW_SESSION=\""; + let begin = output_str.find(session_text).unwrap() + session_text.len(); + let end = output_str[begin..].find('"').unwrap(); + let session_token = output_str[begin..(begin + end)].to_string(); + + let mut storage = self.storage.write().await; + storage.session_token = Some(session_token); + storage.last_unlock = Some(chrono::offset::Utc::now()); + + info!("`bw unlock` succeed"); + Ok(()) + } + Err(err) => { + error!("`bw unlock` failed, {}", err.to_string()); + Err(BitwardenError::UnlockFailed) + } + } + } + + pub async fn sync(&self) -> Result<(), BitwardenError> { + let mut storage = self.storage.write().await; + let Some(session_token) = &storage.session_token else { + return Err(BitwardenError::SyncFailedTokenMissing); + }; + + info!("`bw sync`"); + let cmd = tokio::process::Command::new("bw") + .args(["sync"]) + .env("BW_SESSION", session_token.clone()) + .output() + .await; + + match cmd { + Ok(_) => { + info!("`bw sync` succeed"); + + storage.last_sync = Some(chrono::offset::Utc::now()); + Ok(()) + } + Err(err) => { + error!("`bw sync` failed, {}", err.to_string()); + storage.needs_relog = true; + Err(BitwardenError::SyncFailed) + } + } + } + + pub async fn get_item(&self, item_id: String) -> Result<BitwardenItem, BitwardenError> { + let mut storage = self.storage.write().await; + let Some(session_token) = &storage.session_token else { + return Err(BitwardenError::SyncFailedTokenMissing); + }; + + let cmd = tokio::process::Command::new("bw") + .args(["--response", "get", "item", &item_id, "--nointeraction"]) + .env("BW_SESSION", session_token) + .output() + .await; + + match cmd { + Ok(output) => { + let response = + serde_json::from_slice::<BitwardenGetItemResponse>(output.stdout.as_slice()); + + if response.is_err() { + let error_msg = String::from_utf8(output.stdout).unwrap(); + error!( + "`bw get item {}` failed: {}, body: {}", + item_id, + response.unwrap_err(), + error_msg + ); + return Err(BitwardenError::ItemNotFound(item_id)); + } + + let data = response.unwrap(); + if !data.success { + error!("`bw get item {}` failed, couldn't find item", item_id); + return Err(BitwardenError::ItemNotFound(item_id)); + } + + if data.data.is_none() { + error!("`bw get item {}` failed, couldn't find item", item_id); + return Err(BitwardenError::ItemNotFound(item_id)); + } + + info!("`bw get item {item_id}` succeed"); + + Ok(data.data.unwrap()) + } + Err(err) => { + error!("`bw get item {}` failed, {}", item_id, err.to_string()); + storage.needs_relog = true; + Err(BitwardenError::GetItemGenericFail(item_id, err.to_string())) + } + } + } +} diff --git a/src/crdgen.rs b/src/crdgen.rs new file mode 100644 index 0000000..8bedf6d --- /dev/null +++ b/src/crdgen.rs @@ -0,0 +1,9 @@ +// crdgen.rs +pub mod operator; + +use kube::CustomResourceExt; +use crate::operator::schemas::BitwardenSecret; + +fn main() { + print!("{}", serde_yaml::to_string(&BitwardenSecret::crd()).unwrap()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..110315f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,75 @@ +use axum::routing::get; +use axum::Router; +use kube::Client; +use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; +use std::env; +use std::future::ready; +use std::sync::Arc; +use tokio::join; +use tracing::info; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{filter, Layer}; + +use crate::bitwarden_cli::BitwardenCliClient; +use crate::operator::controller::BitwardenOperator; + +pub mod bitwarden_cli; +pub mod monitoring; +pub mod operator; + +fn setup_metrics_recorder() -> PrometheusHandle { + PrometheusBuilder::new().install_recorder().unwrap() +} + +async fn health() -> &'static str { + "Hello, World!" +} + +async fn start_metrics_server() { + let recorder_handle = setup_metrics_recorder(); + let app = Router::new() + .route("/metrics", get(move || ready(recorder_handle.render()))) + .route("/health", get(health)); + + let metrics_endpoint = + env::var("METRICS_ENDPOINT").unwrap_or_else(|_| "127.0.0.1:3001".to_string()); + let listener = tokio::net::TcpListener::bind(metrics_endpoint) + .await + .unwrap(); + info!( + "HTTP /metrics server listening on: {}", + listener.local_addr().unwrap() + ); + axum::serve(listener, app).await.unwrap(); +} + +#[tokio::main] +async fn main() -> eyre::Result<()> { + let stdout_log = tracing_subscriber::fmt::layer(); + + let stderr_log = tracing_subscriber::fmt::layer(); + let tracer = monitoring::init_tracer().await; + + let registry = tracing_subscriber::Registry::default() + .with(stdout_log.with_filter(filter::LevelFilter::INFO)) + .with(stderr_log.with_filter(filter::LevelFilter::ERROR)); + + if let Some(tracer) = tracer { + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + registry.with(telemetry).init(); + } else { + registry.init(); + } + + let cli = Arc::new(BitwardenCliClient::from_env()?); + cli.login().await?; + cli.unlock().await?; + cli.sync().await?; + + let client = Client::try_default().await?; + + let bitwarden_operator = BitwardenOperator::new(cli, client); + let (_operator, _metrics_server) = join!(bitwarden_operator.start(), start_metrics_server()); + Ok(()) +} diff --git a/src/monitoring.rs b/src/monitoring.rs new file mode 100644 index 0000000..3641902 --- /dev/null +++ b/src/monitoring.rs @@ -0,0 +1,32 @@ +use tracing::info; + +pub(crate) async fn init_tracer() -> Option<opentelemetry_sdk::trace::Tracer> { + let Ok(otlp_endpoint) = std::env::var("OPENTELEMETRY_ENDPOINT_URL") else { + return None; + }; + + info!("Initializing OpenTelemetry Traces client"); + let channel = tonic::transport::Channel::from_shared(otlp_endpoint) + .unwrap() + .connect() + .await + .unwrap(); + + Some( + opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_channel(channel), + ) + .with_trace_config(opentelemetry_sdk::trace::config().with_resource( + opentelemetry_sdk::Resource::new(vec![opentelemetry::KeyValue::new( + "service.name", + "bitwarden-secret-operator-rs", + )]), + )) + .install_batch(opentelemetry_sdk::runtime::Tokio) + .unwrap(), + ) +} diff --git a/src/operator/controller.rs b/src/operator/controller.rs new file mode 100644 index 0000000..5812efd --- /dev/null +++ b/src/operator/controller.rs @@ -0,0 +1,200 @@ +use crate::bitwarden_cli::BitwardenCliClient; +use crate::operator::generate_secret_from_bitwarden_secret; +use crate::operator::schemas::{BitwardenSecret, BitwardenSecretError, BitwardenSecretStatus}; +use chrono::Utc; +use futures::StreamExt; +use k8s_openapi::api::core::v1::Secret; +use kube::api::{Patch, PatchParams, PostParams}; +use kube::runtime::controller::Action; +use kube::runtime::{watcher, Controller}; +use kube::{Api, Client, ResourceExt}; +use serde_json::json; +use std::sync::Arc; +use std::time::Duration; +use tokio::{join, task}; +use tracing::{error, info, warn}; + +pub struct BitwardenOperator { + cli: Arc<BitwardenCliClient>, + client: Client, +} + +#[derive(Clone)] +struct KubeContext { + /// kubernetes client + client: Client, + bitwarden_cli: Arc<BitwardenCliClient>, +} + +impl BitwardenOperator { + pub fn new(cli: Arc<BitwardenCliClient>, client: Client) -> Self { + Self { cli, client } + } + + pub async fn start(&self) -> eyre::Result<()> { + info!("Starting Operator..."); + let context = Arc::new(KubeContext { + client: self.client.clone(), + bitwarden_cli: self.cli.clone(), + }); + + let cli = self.cli.clone(); + + // background task to sync the CLI secrets every X seconds + task::spawn(async move { + let cli = cli.clone(); + loop { + tokio::time::sleep(Duration::from_secs(60)).await; + let _ = cli.sync().await; + } + }); + + // generate secret + let bitwarden_secrets = Api::<BitwardenSecret>::all(self.client.clone()); + let secrets = Api::<Secret>::all(self.client.clone()); + + Controller::new(bitwarden_secrets.clone(), watcher::Config::default()) + .owns(secrets, watcher::Config::default()) + .run(reconcile_bitwarden_secret, error_policy, context) + .for_each(|res| async move { + match res { + Ok(o) => info!("reconciled {}:{}", o.0.namespace.unwrap(), o.0.name), + Err(e) => warn!("reconcile failed: {}", e), + } + }) + .await; + + Ok(()) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum BitwardenOperatorError { + #[error("BitwardenSecretError: {0}, ({0:?})")] + BitwardenSecretError(#[from] BitwardenSecretError), + #[error("KubernetesClientError: {0} ({0:?})")] + KubernetesError(#[from] kube::error::Error), +} + +pub type BitwardenOperatorResult<T, E = BitwardenOperatorError> = Result<T, E>; + +async fn reconcile_bitwarden_secret( + obj: Arc<BitwardenSecret>, + ctx: Arc<KubeContext>, +) -> BitwardenOperatorResult<Action> { + let manifest_name = &obj.name_any(); + info!("reconcile request: {}", manifest_name); + metrics::counter!("reconcile_requests_total").increment(1); + + // avoid refreshing if unnecessary + if let Some(status) = &obj.status { + // TODO configuration later + let now = Utc::now(); + if status + .last_updated + .is_some_and(|x| now < x + Duration::from_secs(3600)) + { + // TODO configuration later + return Ok(Action::requeue(Duration::from_secs(60))); + } + }; + + let target_namespace = &obj + .spec + .namespace + .clone() + .unwrap_or_else(|| obj.namespace().unwrap()); + let secret_name = obj.spec.name.clone().unwrap_or_else(|| obj.name_any()); + + let namespace = Api::<Secret>::namespaced(ctx.client.clone(), target_namespace); + let (present_secret_result, expected_secret_result) = join!( + namespace.get_opt(&secret_name), + generate_secret_from_bitwarden_secret(ctx.bitwarden_cli.clone(), obj.clone()) + ); + + let secret = match expected_secret_result { + Ok(secret) => secret, + Err(e) => { + // Log the error and return early with Err + error!( + "Failed to reconcile BitwardenSecret: {}, {}", + manifest_name, + e.to_string() + ); + return Err(BitwardenOperatorError::BitwardenSecretError(e)); + } + }; + + if present_secret_result?.is_some() { + info!( + "Secret: {} - {} replacing...", + secret.name_any(), + secret.namespace().unwrap() + ); + namespace + .replace( + &secret.name_any(), + &PostParams { + dry_run: false, + field_manager: Default::default(), + }, + &secret, + ) + .await?; + info!( + "Secret: {} - {} replaced!", + secret.name_any(), + secret.namespace().unwrap() + ); + } else { + info!( + "Secret: {} - {} creating...", + secret.name_any(), + secret.namespace().unwrap() + ); + namespace + .create( + &PostParams { + dry_run: false, + field_manager: Default::default(), + }, + &secret, + ) + .await?; + info!( + "Secret: {} - {} created!", + secret.name_any(), + secret.namespace().unwrap() + ); + } + + let status = json!({ + "status": BitwardenSecretStatus { + checksum: "todo".to_string(), + last_updated: Some(Utc::now()), + } + }); + + let namespace = &obj.namespace().unwrap(); + info!("BitwardenSecret: {} updating status...", obj.name_any()); + let api = Api::<BitwardenSecret>::namespaced(ctx.client.clone(), namespace); + api.patch_status( + &obj.name_any(), + &PatchParams::default(), + &Patch::Merge(&status), + ) + .await?; + info!("BitwardenSecret: {} status updated!", obj.name_any()); + + metrics::counter!("reconcile_requests_success_total").increment(1); + Ok(Action::await_change()) +} + +fn error_policy( + _object: Arc<BitwardenSecret>, + _err: &BitwardenOperatorError, + _ctx: Arc<KubeContext>, +) -> Action { + metrics::counter!("reconcile_errors_total").increment(1); + Action::requeue(Duration::from_secs(5)) +} diff --git a/src/operator/mod.rs b/src/operator/mod.rs new file mode 100644 index 0000000..eadfb52 --- /dev/null +++ b/src/operator/mod.rs @@ -0,0 +1,181 @@ +pub mod controller; +pub mod schemas; + +use crate::bitwarden_cli::{BitwardenCliClient, BitwardenItem}; +use crate::operator::schemas::{ + BitwardenSecret, BitwardenSecretError, BitwardenSecretSpec, ContentEntry, +}; +use k8s_openapi::api::core::v1::Secret; +use k8s_openapi::ByteString; +use kube::{Resource, ResourceExt}; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::sync::Arc; + +fn get_bitwarden_id( + content_entry: &ContentEntry, + bitwarden_secret: &BitwardenSecret, +) -> Result<String, BitwardenSecretError> { + content_entry + .bitwarden_id + .clone() + .or_else(|| bitwarden_secret.spec.bitwarden_id.clone()) + .ok_or_else(|| { + BitwardenSecretError::MissingBitwardenId(content_entry.kubernetes_secret_key.clone()) + }) +} + +fn get_secret_value( + content_entry: &ContentEntry, + bitwarden_item: &BitwardenItem, + bitwarden_id: &str, +) -> Result<String, BitwardenSecretError> { + if let Some(value) = &content_entry.kubernetes_secret_value { + return Ok(value.clone()); + } + + if let Some(use_note) = content_entry.bitwarden_use_note { + return if use_note { + // If bitwarden_use_note is true, try to return the note. + bitwarden_item.note.clone().ok_or_else(|| { + BitwardenSecretError::WrongValues( + bitwarden_id.to_string(), + "bitwarden_use_note".to_string(), + ) + }) + } else { + // If bitwarden_use_note is false, return an error. + Err(BitwardenSecretError::WrongValues( + bitwarden_id.to_string(), + "bitwarden_use_note".to_string(), + )) + }; + } + + if let Some(field_name) = &content_entry.bitwarden_secret_field { + if let Some(fields) = &bitwarden_item.fields { + let item_field = fields + .iter() + .find(|x| &x.name == field_name) + .ok_or_else(|| { + BitwardenSecretError::BitwardenItemNotFound(field_name.to_string()) + })?; + return Ok(item_field.value.clone()); + } + } + + Err(BitwardenSecretError::WrongValues( + bitwarden_id.to_string(), + "bitwarden_use_note".to_string(), + )) +} + +pub async fn generate_secret_from_bitwarden_secret( + cli: Arc<BitwardenCliClient>, + bitwarden_secret: Arc<BitwardenSecret>, +) -> Result<Secret, BitwardenSecretError> { + let mut secret = Secret::default(); + secret.metadata.name = Some( + bitwarden_secret + .spec + .name + .clone() + .unwrap_or_else(|| bitwarden_secret.metadata.name.clone().unwrap()), + ); + secret.metadata.namespace = Some( + bitwarden_secret + .spec + .namespace + .clone() + .unwrap_or_else(|| bitwarden_secret.metadata.namespace.clone().unwrap()), + ); + + let oref = bitwarden_secret.controller_owner_ref(&()).unwrap(); + secret.owner_references_mut().push(oref); + + let global_bitwarden_id = bitwarden_secret.spec.bitwarden_id.clone(); + let to_fetch = try_get_to_fetch( + &bitwarden_secret, + &bitwarden_secret.spec, + global_bitwarden_id, + )?; + + // get all bitwarden needed secrets + let mut fetched = HashMap::<String, BitwardenItem>::new(); + for element in to_fetch { + let item = cli + .get_item(element.clone()) + .await + .map_err(|_e| BitwardenSecretError::BitwardenItemNotFound(element.clone()))?; + fetched.insert(element.clone(), item); + } + + let secret_data = generate_secret_data(&bitwarden_secret, &mut fetched)?; + secret.data = Some(secret_data); + + let mut string_data = BTreeMap::<String, String>::new(); + if let Some(bw_string_data) = &bitwarden_secret.spec.string_data { + for x in bw_string_data { + string_data.insert(x.0.clone(), x.1.clone()); + } + } + secret.string_data = Some(string_data); + + let mut labels = match secret.metadata.labels { + Some(ref x) => x.clone(), + None => BTreeMap::new(), + }; + + let now = chrono::offset::Utc::now(); + labels.insert(schemas::OPERATOR_HASH_LABEL.to_string(), "test".to_string()); + labels.insert( + schemas::OPERATOR_LAST_UPDATE_LABEL.to_string(), + now.timestamp().to_string(), + ); + secret.metadata.labels = Some(labels); + Ok(secret) +} + +fn generate_secret_data( + bitwarden_secret: &Arc<BitwardenSecret>, + fetched: &mut HashMap<String, BitwardenItem>, +) -> Result<BTreeMap<String, ByteString>, BitwardenSecretError> { + let mut secret_data = BTreeMap::<String, ByteString>::new(); + for entry in &bitwarden_secret.spec.content { + let bitwarden_data = fetched + .get(&get_bitwarden_id(entry, bitwarden_secret)?) + .ok_or_else(|| { + BitwardenSecretError::MissingBitwardenId(entry.kubernetes_secret_key.clone()) + })?; + + let bitwarden_id = &get_bitwarden_id(entry, bitwarden_secret)?; + let secret_value = get_secret_value(entry, bitwarden_data, bitwarden_id)?; + + secret_data.insert( + entry.kubernetes_secret_key.clone(), + ByteString(secret_value.as_bytes().to_vec()), + ); + } + Ok(secret_data) +} + +fn try_get_to_fetch( + bitwarden_secret: &Arc<BitwardenSecret>, + bitwarden_spec: &BitwardenSecretSpec, + global_bitwarden_id: Option<String>, +) -> Result<HashSet<String>, BitwardenSecretError> { + let mut to_fetch = HashSet::<String>::new(); + + for content in &bitwarden_spec.content { + if content.bitwarden_id.is_none() + && content.kubernetes_secret_value.is_none() + && global_bitwarden_id.is_none() + { + return Err(BitwardenSecretError::MissingBitwardenId( + content.kubernetes_secret_key.clone(), + )); + } + + to_fetch.insert(get_bitwarden_id(content, bitwarden_secret)?); + } + Ok(to_fetch) +} diff --git a/src/operator/schemas.rs b/src/operator/schemas.rs new file mode 100644 index 0000000..6627b41 --- /dev/null +++ b/src/operator/schemas.rs @@ -0,0 +1,74 @@ +use chrono::{DateTime, Utc}; +use kube::CustomResource; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use thiserror::Error; + +#[derive(CustomResource, Debug, Serialize, Deserialize, Default, Clone, JsonSchema)] +#[kube( + group = "bitwarden-secret-operator.io", + version = "v1beta1", + kind = "BitwardenSecret", + namespaced +)] +#[kube(status = "BitwardenSecretStatus")] +#[serde(rename_all = "camelCase")] +pub struct BitwardenSecretSpec { + #[serde(rename = "name")] + pub name: Option<String>, + + #[serde(rename = "namespace")] + pub namespace: Option<String>, + + #[serde(rename = "type")] + pub secret_type: Option<String>, + + #[serde(rename = "bitwardenId")] + pub bitwarden_id: Option<String>, + + #[serde(rename = "labels")] + pub labels: Option<HashMap<String, String>>, + + #[serde(rename = "content")] + pub content: Vec<ContentEntry>, + + #[serde(rename = "stringData")] + pub string_data: Option<HashMap<String, String>>, +} + +#[derive(Deserialize, Serialize, Clone, Debug, JsonSchema)] +pub struct BitwardenSecretStatus { + pub checksum: String, + pub last_updated: Option<DateTime<Utc>>, +} + +#[derive(Debug, Serialize, Deserialize, Default, Clone, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct ContentEntry { + #[serde(rename = "bitwardenId")] + pub bitwarden_id: Option<String>, + #[serde(rename = "bitwardenSecretField")] + pub bitwarden_secret_field: Option<String>, + #[serde(rename = "bitwardenUseNote")] + pub bitwarden_use_note: Option<bool>, + #[serde(rename = "kubernetesSecretKey")] + pub kubernetes_secret_key: String, + #[serde(rename = "kubernetesSecretValue")] + pub kubernetes_secret_value: Option<String>, +} + +#[derive(Error, Debug)] +pub enum BitwardenSecretError { + #[error("The given Kubernetes secret key seems misconfigured {0}")] + MissingBitwardenId(String), + + #[error("Bitwarden Item: {0} not found")] + BitwardenItemNotFound(String), + + #[error("Bitwarden Item: {0}, error on field: {1}")] + WrongValues(String, String), +} + +pub(crate) const OPERATOR_HASH_LABEL: &str = "bitwarden-secret-operator-rs.io/hash"; +pub(crate) const OPERATOR_LAST_UPDATE_LABEL: &str = "bitwarden-secret-operator-rs.io/last-update";