File vault-sync-0.11.0.obscpio of Package vault-sync

07070100000000000081A40000000000000000000000016815658100000010000000000000000000000000000000000000002000000000vault-sync-0.11.0/.dockerignoretarget/
docker/
07070100000001000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000001A00000000vault-sync-0.11.0/.github07070100000002000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000002400000000vault-sync-0.11.0/.github/workflows07070100000003000081A400000000000000000000000168156581000021EF000000000000000000000000000000000000002B00000000vault-sync-0.11.0/.github/workflows/ci.ymlname: CI

on:
  push:
    branches:
      - main
    tags:
      - '*'
  pull_request:
    branches:
      - main

env:
  CARGO_TERM_COLOR: always
  VAULT_ADDR: http://127.0.0.1:8200

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Build
      run: cargo build --verbose

    - name: Run tests
      run: cargo test --verbose

    - name: Install Vault
      run: |
        sudo apt-get update -y
        sudo apt-get install -y gpg
        wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg >/dev/null
        gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint
        echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
        sudo apt-get update -y
        sudo apt-get install -y vault

    - name: Run local test
      run: ./scripts/test.sh

    - name: Run Vault in background
      run: |
        vault server -dev -dev-root-token-id=unsafe-root-token &
        # Make sure Vault is running
        sleep 1
        while ! vault token lookup; do sleep 1; done

    - name: Enable AppRole auth method
      run: |
        vault auth enable approle

    - name: Enable secrets engine at the custom path
      run: |
        vault secrets enable -path=secret2 kv-v2

    - name: Make sure both standard and custom secrets engine are present
      run: |
        vault secrets list
        vault secrets list | grep -qE '^secret/\s+kv'
        vault secrets list | grep -qE '^secret2/\s+kv'

    - name: Create Vault policy for reader
      run: |
        cat <<EOF | vault policy write vault-sync-reader -
          path "secret/data/*" {
            capabilities = ["read", "list"]
          }
          path "secret/metadata/*" {
            capabilities = ["read", "list"]
          }
          path "secret2/data/*" {
            capabilities = ["read", "list"]
          }
          path "secret2/metadata/*" {
            capabilities = ["read", "list"]
          }
        EOF

    - name: Create Vault policy for writer
      run: |
        cat <<EOF | vault policy write vault-sync-writer -
          path "secret/data/*" {
            capabilities = ["create", "read", "update", "delete"]
          }
          path "secret2/data/*" {
            capabilities = ["create", "read", "update", "delete"]
          }
        EOF

    - name: Create Vault AppRoles for reader and writer
      run: |
        vault write auth/approle/role/vault-sync-reader token_policies=vault-sync-reader
        vault write auth/approle/role/vault-sync-writer token_policies=vault-sync-writer

    - name: Prepare environments variables for token authentication
      run: |
        cat <<EOF > ./vault-sync-token.env
        export VAULT_SYNC_SRC_TOKEN=unsafe-root-token
        export VAULT_SYNC_DST_TOKEN=unsafe-root-token
        EOF

    - name: Prepare environments variables for approle authentication
      run: |
        cat <<EOF > ./vault-sync-app-role.env
        export VAULT_SYNC_SRC_ROLE_ID=$(vault read auth/approle/role/vault-sync-reader/role-id -format=json | jq -r .data.role_id)
        export VAULT_SYNC_SRC_SECRET_ID=$(vault write -f auth/approle/role/vault-sync-reader/secret-id -format=json | jq -r .data.secret_id)
        export VAULT_SYNC_DST_ROLE_ID=$(vault read auth/approle/role/vault-sync-writer/role-id -format=json | jq -r .data.role_id)
        export VAULT_SYNC_DST_SECRET_ID=$(vault write -f auth/approle/role/vault-sync-writer/secret-id -format=json | jq -r .data.secret_id)
        EOF

    - name: Create configuration file for vault-sync
      run: |
        cat <<EOF > ./vault-sync.yaml
        id: vault-sync
        full_sync_interval: 10
        src:
          url: http://127.0.0.1:8200/
          prefix: src
        dst:
          url: http://127.0.0.1:8200/
          prefix: dst
        EOF

    - name: Test sync once with token
      run: |
        vault kv put secret/src/testsecret1 foo1=bar1
        source ./vault-sync-token.env
        cargo run -- --config vault-sync.yaml --once
        vault kv get secret/dst/testsecret1

    - name: Test sync once with approle
      run: |
        vault kv put secret/src/testsecret2 foo2=bar2
        source ./vault-sync-app-role.env
        cargo run -- --config vault-sync.yaml --once
        vault kv get secret/dst/testsecret2

    - name: Create configuration file for vault-sync (custom secrets engine)
      run: |
        cat <<EOF > ./vault-sync.yaml
        id: vault-sync
        full_sync_interval: 10
        src:
          url: http://127.0.0.1:8200/
          prefix: src
          backend: secret2
        dst:
          url: http://127.0.0.1:8200/
          prefix: dst
          backend: secret2
        EOF

    - name: Test sync once with token
      run: |
        vault kv put -mount secret2 src/testsecret1 foo1=bar1
        source ./vault-sync-token.env
        cargo run -- --config vault-sync.yaml --once
        vault kv get -mount secret2 dst/testsecret1

    - name: Test sync once with approle
      run: |
        vault kv put -mount secret2 src/testsecret2 foo2=bar2
        source ./vault-sync-app-role.env
        cargo run -- --config vault-sync.yaml --once
        vault kv get -mount secret dst/testsecret2

  kubernetes:
    runs-on: ubuntu-latest

    steps:
      - name: Create Kubernetes cluster
        run: |
          cat <<EOF | kind create cluster --config=-
          kind: Cluster
          apiVersion: kind.x-k8s.io/v1alpha4
          nodes:
          - role: control-plane
            image: kindest/node:v1.29.4
          EOF

      - name: Deploy Vault
        run: |
          kubectl create ns vault
          helm repo add hashicorp https://helm.releases.hashicorp.com
          helm upgrade --install vault hashicorp/vault --namespace=vault --set server.dev.enabled=true --set injector.enabled=false

      - name: Wait for Vault readiness
        run: |
          kubectl --namespace=vault get service
          for i in $(seq 1 30); do
            if kubectl --namespace=vault get pods | grep -q vault-0 &>/dev/null; then
              break
            fi
            sleep 1
          done
          kubectl --namespace=vault wait pod/vault-0 --for=condition=Ready --timeout=180s
          kubectl --namespace=vault logs vault-0

      - name: Create secret backends
        run: |
          kubectl --namespace=vault exec vault-0 -- vault secrets enable -version=2 -path=src kv
          kubectl --namespace=vault exec vault-0 -- vault secrets enable -version=2 -path=dst kv
          kubectl --namespace=vault exec vault-0 -- vault kv put -mount src test foo=bar

      - uses: actions/checkout@v4

      - name: Build Docker image
        run: |
          docker build -t pbchekin/vault-sync:$GITHUB_SHA -f docker/Dockerfile .

      - name: Load Docker image to the cluster
        run: |
          kind load docker-image pbchekin/vault-sync:$GITHUB_SHA

      - name: Deploy vault-sync
        run: |
          kubectl create ns vault-sync
          cd install/helm/vault-sync/
          helm install --namespace=vault-sync vault-sync -f - . <<EOF
          image:
            tag: $GITHUB_SHA
          vaultSync:
            id: vault-sync
            full_sync_interval: 3600
            src:
              url: http://vault.vault.svc.cluster.local:8200
              backend: src
            dst:
              url: http://vault.vault.svc.cluster.local:8200
              backend: dst
          # Secrets must be base64 encoded
          secrets:
            VAULT_SYNC_SRC_TOKEN: cm9vdA==
            VAULT_SYNC_DST_TOKEN: cm9vdA==
          EOF

      - name: Wait for vault-sync readiness
        run: |
          for i in $(seq 1 30); do
            if kubectl --namespace=vault-sync get pods | grep -q vault-sync &>/dev/null; then
              break
            fi
            sleep 1
          done
          if ! kubectl --namespace=vault-sync wait pod -l app.kubernetes.io/instance=vault-sync --for=condition=Ready --timeout=180s; then
            kubectl get pods -A
            kubectl --namespace=vault-sync logs -l app.kubernetes.io/instance=vault-sync
            exit 1
          fi

      - name: Check sync result
        run: |
          # wait for the initial sync
          sleep 5
          kubectl --namespace=vault exec vault-0 -- vault kv get -mount dst test

      - name: Show vault-sync logs
        run: |
          kubectl --namespace=vault-sync logs -l app.kubernetes.io/instance=vault-sync
07070100000004000081A40000000000000000000000016815658100000481000000000000000000000000000000000000002F00000000vault-sync-0.11.0/.github/workflows/docker.ymlname: Docker

on:
  push:
    branches:
      - main
    tags:
      - '*'

jobs:
  docker:
    name: Docker
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Set Docker Tag
        id: tag
        run: |
          if [[ $GITHUB_REF == refs/heads/main ]]; then
            DOCKER_TAG="main"
          else
            DOCKER_TAG="$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml)"
          fi
          echo "tag=${DOCKER_TAG}" >> $GITHUB_OUTPUT

      - name: Build Docker Image
        run: docker build -t pbchekin/vault-sync:${{ steps.tag.outputs.tag }} -f docker/Dockerfile .

      - name: Login to Docker Hub
        if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
        run: docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password ${{ secrets.DOCKERHUB_PASSWORD }}

      - name: Push Docker Image
        if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
        run: docker push pbchekin/vault-sync:${{ steps.tag.outputs.tag }}
07070100000005000081A40000000000000000000000016815658100000035000000000000000000000000000000000000001D00000000vault-sync-0.11.0/.gitignore/target
/.idea
vault-sync.yaml
vault*.log
vault*.pid
07070100000006000081A4000000000000000000000001681565810000B172000000000000000000000000000000000000001D00000000vault-sync-0.11.0/Cargo.lock# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
 "gimli",
]

[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"

[[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 = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
 "winapi",
]

[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
 "hermit-abi",
 "libc",
 "winapi",
]

[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"

[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
 "addr2line",
 "cfg-if",
 "libc",
 "miniz_oxide",
 "object",
 "rustc-demangle",
 "windows-targets 0.52.6",
]

[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"

[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"

[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"

[[package]]
name = "bytes"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"

[[package]]
name = "cc"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
 "shlex",
]

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"

[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
 "android-tzdata",
 "iana-time-zone",
 "js-sys",
 "num-traits",
 "wasm-bindgen",
 "windows-link",
]

[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
 "ansi_term",
 "atty",
 "bitflags 1.3.2",
 "strsim",
 "textwrap",
 "unicode-width",
 "vec_map",
]

[[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.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"

[[package]]
name = "ctrlc"
version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
 "nix",
 "windows-sys 0.59.0",
]

[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
 "powerfmt",
]

[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
 "cfg-if",
]

[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"

[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
 "libc",
 "windows-sys 0.59.0",
]

[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"

[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"

[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
 "foreign-types-shared",
]

[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"

[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
 "percent-encoding",
]

[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
 "futures-core",
]

[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"

[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"

[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"

[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"

[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
 "futures-core",
 "futures-io",
 "futures-task",
 "memchr",
 "pin-project-lite",
 "pin-utils",
 "slab",
]

[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
 "cfg-if",
 "libc",
 "wasi 0.13.3+wasi-0.2.2",
 "windows-targets 0.52.6",
]

[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"

[[package]]
name = "h2"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
 "bytes",
 "fnv",
 "futures-core",
 "futures-sink",
 "futures-util",
 "http",
 "indexmap",
 "slab",
 "tokio",
 "tokio-util",
 "tracing",
]

[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"

[[package]]
name = "hashicorp_vault"
version = "2.1.1"
dependencies = [
 "base64 0.13.1",
 "chrono",
 "log",
 "quick-error",
 "reqwest",
 "serde",
 "serde_derive",
 "serde_json",
 "url",
]

[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
 "libc",
]

[[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-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
 "bytes",
 "http",
 "pin-project-lite",
]

[[package]]
name = "httparse"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"

[[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.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
 "bytes",
 "futures-channel",
 "futures-core",
 "futures-util",
 "h2",
 "http",
 "http-body",
 "httparse",
 "httpdate",
 "itoa",
 "pin-project-lite",
 "socket2",
 "tokio",
 "tower-service",
 "tracing",
 "want",
]

[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
 "bytes",
 "hyper",
 "native-tls",
 "tokio",
 "tokio-native-tls",
]

[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
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 = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
 "displaydoc",
 "yoke",
 "zerofrom",
 "zerovec",
]

[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
 "displaydoc",
 "litemap",
 "tinystr",
 "writeable",
 "zerovec",
]

[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
 "displaydoc",
 "icu_locid",
 "icu_locid_transform_data",
 "icu_provider",
 "tinystr",
 "zerovec",
]

[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"

[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
 "displaydoc",
 "icu_collections",
 "icu_normalizer_data",
 "icu_properties",
 "icu_provider",
 "smallvec",
 "utf16_iter",
 "utf8_iter",
 "write16",
 "zerovec",
]

[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"

[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
 "displaydoc",
 "icu_collections",
 "icu_locid_transform",
 "icu_properties_data",
 "icu_provider",
 "tinystr",
 "zerovec",
]

[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"

[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
 "displaydoc",
 "icu_locid",
 "icu_provider_macros",
 "stable_deref_trait",
 "tinystr",
 "writeable",
 "yoke",
 "zerofrom",
 "zerovec",
]

[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
 "idna_adapter",
 "smallvec",
 "utf8_iter",
]

[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
 "icu_normalizer",
 "icu_properties",
]

[[package]]
name = "indexmap"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
 "equivalent",
 "hashbrown",
]

[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"

[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"

[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
 "once_cell",
 "wasm-bindgen",
]

[[package]]
name = "libc"
version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"

[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"

[[package]]
name = "litemap"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"

[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"

[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"

[[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.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
 "adler2",
]

[[package]]
name = "mio"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
 "libc",
 "wasi 0.11.0+wasi-snapshot-preview1",
 "windows-sys 0.52.0",
]

[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
 "libc",
 "log",
 "openssl",
 "openssl-probe",
 "openssl-sys",
 "schannel",
 "security-framework",
 "security-framework-sys",
 "tempfile",
]

[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
 "bitflags 2.9.0",
 "cfg-if",
 "cfg_aliases",
 "libc",
]

[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"

[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
 "autocfg",
]

[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
 "libc",
]

[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
 "memchr",
]

[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"

[[package]]
name = "openssl"
version = "0.10.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
dependencies = [
 "bitflags 2.9.0",
 "cfg-if",
 "foreign-types",
 "libc",
 "once_cell",
 "openssl-macros",
 "openssl-sys",
]

[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"

[[package]]
name = "openssl-sys"
version = "0.9.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
dependencies = [
 "cc",
 "libc",
 "pkg-config",
 "vcpkg",
]

[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"

[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"

[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"

[[package]]
name = "pkg-config"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"

[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"

[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"

[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "reqwest"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
 "base64 0.21.7",
 "bytes",
 "encoding_rs",
 "futures-core",
 "futures-util",
 "h2",
 "http",
 "http-body",
 "hyper",
 "hyper-tls",
 "ipnet",
 "js-sys",
 "log",
 "mime",
 "native-tls",
 "once_cell",
 "percent-encoding",
 "pin-project-lite",
 "rustls-pemfile",
 "serde",
 "serde_json",
 "serde_urlencoded",
 "sync_wrapper",
 "system-configuration",
 "tokio",
 "tokio-native-tls",
 "tower-service",
 "url",
 "wasm-bindgen",
 "wasm-bindgen-futures",
 "web-sys",
 "winreg",
]

[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"

[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
 "bitflags 2.9.0",
 "errno",
 "libc",
 "linux-raw-sys",
 "windows-sys 0.59.0",
]

[[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 = "rustversion"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"

[[package]]
name = "ryu"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"

[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
 "windows-sys 0.59.0",
]

[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
 "bitflags 2.9.0",
 "core-foundation",
 "core-foundation-sys",
 "libc",
 "security-framework-sys",
]

[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
 "core-foundation-sys",
 "libc",
]

[[package]]
name = "serde"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "serde_json"
version = "1.0.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
dependencies = [
 "itoa",
 "memchr",
 "ryu",
 "serde",
]

[[package]]
name = "serde_repr"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[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",
 "itoa",
 "ryu",
 "serde",
 "unsafe-libyaml",
]

[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"

[[package]]
name = "simplelog"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0"
dependencies = [
 "log",
 "termcolor",
 "time",
]

[[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.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"

[[package]]
name = "socket2"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
 "libc",
 "windows-sys 0.52.0",
]

[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"

[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"

[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
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 = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
 "bitflags 1.3.2",
 "core-foundation",
 "system-configuration-sys",
]

[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
 "core-foundation-sys",
 "libc",
]

[[package]]
name = "tempfile"
version = "3.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230"
dependencies = [
 "cfg-if",
 "fastrand",
 "getrandom",
 "once_cell",
 "rustix",
 "windows-sys 0.59.0",
]

[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
 "winapi-util",
]

[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
 "unicode-width",
]

[[package]]
name = "time"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
 "deranged",
 "itoa",
 "libc",
 "num-conv",
 "num_threads",
 "powerfmt",
 "serde",
 "time-core",
 "time-macros",
]

[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"

[[package]]
name = "time-macros"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
 "num-conv",
 "time-core",
]

[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
 "displaydoc",
 "zerovec",
]

[[package]]
name = "tokio"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
 "backtrace",
 "bytes",
 "libc",
 "mio",
 "pin-project-lite",
 "socket2",
 "windows-sys 0.52.0",
]

[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
 "native-tls",
 "tokio",
]

[[package]]
name = "tokio-util"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
 "bytes",
 "futures-core",
 "futures-sink",
 "pin-project-lite",
 "tokio",
]

[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"

[[package]]
name = "tracing"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
 "pin-project-lite",
 "tracing-core",
]

[[package]]
name = "tracing-core"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
 "once_cell",
]

[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"

[[package]]
name = "unicode-ident"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"

[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"

[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"

[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
 "form_urlencoded",
 "idna",
 "percent-encoding",
]

[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"

[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"

[[package]]
name = "vault-sync"
version = "0.11.0"
dependencies = [
 "clap",
 "ctrlc",
 "hashicorp_vault",
 "log",
 "serde",
 "serde_json",
 "serde_repr",
 "serde_yaml",
 "simplelog",
]

[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"

[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"

[[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 = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
 "wit-bindgen-rt",
]

[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
 "cfg-if",
 "once_cell",
 "rustversion",
 "wasm-bindgen-macro",
]

[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
 "bumpalo",
 "log",
 "proc-macro2",
 "quote",
 "syn",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-futures"
version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [
 "cfg-if",
 "js-sys",
 "once_cell",
 "wasm-bindgen",
 "web-sys",
]

[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
 "quote",
 "wasm-bindgen-macro-support",
]

[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "wasm-bindgen-backend",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
 "js-sys",
 "wasm-bindgen",
]

[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
 "winapi-i686-pc-windows-gnu",
 "winapi-x86_64-pc-windows-gnu",
]

[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"

[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
 "windows-sys 0.59.0",
]

[[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.6",
]

[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"

[[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.6",
]

[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
 "windows-targets 0.52.6",
]

[[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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
 "windows_aarch64_gnullvm 0.52.6",
 "windows_aarch64_msvc 0.52.6",
 "windows_i686_gnu 0.52.6",
 "windows_i686_gnullvm",
 "windows_i686_msvc 0.52.6",
 "windows_x86_64_gnu 0.52.6",
 "windows_x86_64_gnullvm 0.52.6",
 "windows_x86_64_msvc 0.52.6",
]

[[package]]
name = "windows_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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"

[[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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"

[[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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"

[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"

[[package]]
name = "windows_i686_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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"

[[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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"

[[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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"

[[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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
 "cfg-if",
 "windows-sys 0.48.0",
]

[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
 "bitflags 2.9.0",
]

[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"

[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"

[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
 "serde",
 "stable_deref_trait",
 "yoke-derive",
 "zerofrom",
]

[[package]]
name = "yoke-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "synstructure",
]

[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
 "zerofrom-derive",
]

[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "synstructure",
]

[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
 "yoke",
 "zerofrom",
 "zerovec-derive",
]

[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]
07070100000007000081A4000000000000000000000001681565810000019C000000000000000000000000000000000000001D00000000vault-sync-0.11.0/Cargo.toml[package]
name = "vault-sync"
version = "0.11.0"
authors = ["Pavel Chekin <pbchekin@gmail.com>"]
edition = "2021"

[dependencies]
clap = "2.34.0"
ctrlc = { version = "3.2.3", features = ["termination"] }
log = "0.4.17"
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.107"
serde_repr = "0.1.16"
serde_yaml = "0.9.25"
simplelog = "0.12.0"

[dependencies.hashicorp_vault]
path = "vault-rs"
07070100000008000081A40000000000000000000000016815658100002C4F000000000000000000000000000000000000001A00000000vault-sync-0.11.0/LICENSE
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2021 Pavel Chekin

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
07070100000009000081A40000000000000000000000016815658100001AA1000000000000000000000000000000000000001C00000000vault-sync-0.11.0/README.md# vault-sync

A poor man's tool to replicate secrets from one Vault instance to another.

## How it works

When vault-sync starts, it does a full copy of the secrets from the source Vault instance to the destination Vault instance.
Periodically, vault-sync does a full reconciliation to make sure all the destination secrets are up to date.

At the same time, you can manually enable the [Socket Audit Device](https://www.vaultproject.io/docs/audit/socket) for the source Vault,
so Vault will be sending audit logs to vault-sync.
Using these audit logs, vault-sync keeps the secrets in the destination Vault up to date.
Note that vault-sync does not create or delete the audit devices by itself.

It is possible to use the same Vault instance as the source and the destination.
You can use this feature to replicate a "folder" of secrets to another "folder" on the same server.
You need to specify different prefixes (`src.prefix` and `dst.prefix`) in the configuration file to make sure the source and the destination do not overlap.

## Limitations

* Only two Vault auth methods are supported: [Token](https://www.vaultproject.io/docs/auth/token) and [AppRole](https://www.vaultproject.io/docs/auth/approle)
* Only secrets are replicated (specifically their latest versions)

## Configuration

Use the [example](vault-sync.example.yaml) to create your own configuration file.
Instead of specifying secrets in the configuration file, you can use environment variables:

* For Token auth method:
  * `VAULT_SYNC_SRC_TOKEN`
  * `VAULT_SYNC_DST_TOKEN`
* For AppRole auth method:
  * `VAULT_SYNC_SRC_ROLE_ID`
  * `VAULT_SYNC_SRC_SECRET_ID`
  * `VAULT_SYNC_DST_ROLE_ID`
  * `VAULT_SYNC_DST_SECRET_ID`

### Source Vault

A token or AppRole for the source Vault should have a policy that allows listing and reading secrets:

For [KV secrets engine v1](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v1):

```shell
cat <<EOF | vault policy write vault-sync-src -
path "secret/*" {
  capabilities = ["read", "list"]
}
EOF
```

For [KV secrets engine v2](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2):

```shell
cat <<EOF | vault policy write vault-sync-src -
path "secret/data/*" {
  capabilities = ["read", "list"]
}
path "secret/metadata/*" {
  capabilities = ["read", "list"]
}
EOF
```

If the secrets engine mounted to a custom path instead of "secret", then replace "secret" above with the custom path.

To create a token for vault-sync for the source Vault:

```shell
vault token create -policy=vault-sync-src
```

To enable AppRole auth method and create AppRole for vault-sync for the source Vault:

```shell
# Enable approle auth method
vault auth enable approle
# Create a new approle and assign the policy
vault write auth/approle/role/vault-sync-src token_policies=vault-sync-src
# Get role id
vault read auth/approle/role/vault-sync-src/role-id
# Get secret id
vault write -f auth/approle/role/vault-sync-src/secret-id
```

Enabling audit log:

if you want to use the Vault audit device for vault-sync, then you need to create an audit device that always works.
If you have only one audit device enabled, and it is not working (for example, vault-sync has terminated), then Vault will be unresponsive.
Vault will not complete any requests until the audit device can write.
If you have more than one audit device, then Vault will complete the request as long as one audit device persists the log.
The simples way to create an audit device that always works:

```shell
vault audit enable -path stdout file file_path=stdout
```

Then, when vault-sync is running, create the audit device that will be sending audit logs to vault-sync:

```shell
vault audit enable -path vault-sync socket socket_type=tcp address=vault-sync:8202
```

The device name is `vault-sync`, use the same value as specified for `id` in the configuration file.
For `address`, specify the external endpoint for vault-sync.
Note that vault-sync should be running and accessible via the specified address, otherwise Vault will not create the audit device.

### Destination Vault

A token or AppRole for the source Vault should have a policy that allows operations on secrets:

For [KV secrets engine v1](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v1):

```shell
cat <<EOF | vault policy write vault-sync-dst -
path "secret/*" {
  capabilities = ["create", "read", "update", "delete"]
}
EOF
```

For [KV secrets engine v2](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v2):

```shell
cat <<EOF | vault policy write vault-sync-dst -
path "secret/data/*" {
  capabilities = ["create", "read", "update", "delete"]
}
EOF
```

If the secrets engine mounted to a custom path instead of "secret", then replace "secret" above with the custom path.

To create a token for vault-sync for the source Vault:

```shell
vault token create -policy=vault-sync-dst
```

To enable AppRole auth method and create AppRole for vault-sync for the source Vault:

```shell
# Enable approle auth method
vault auth enable approle
# Create a new approle and assign the policy
vault write auth/approle/role/vault-sync-dst token_policies=vault-sync-dst
# Get role id
vault read auth/approle/role/vault-sync-dst/role-id
# Get secret id
vault write -f auth/approle/role/vault-sync-dst/secret-id
```

## Running

```shell
vault-sync --config vault-sync.yaml
```

Command line options:

* `--dry-run` vault-sync shows all the changes it is going to make to the destination Vault, but does not do any actual changes.
* `--once` runs the full sync once, then exits.

## Installation

### From source code

```shell
cargo build --release
```

### Docker

Assuming your configuration file `vault-sync.yaml` is in the current directory: 

```shell
docker run -it -v $PWD:/vault-sync pbchekin/vault-sync:0.8.0 \
  vault-sync --config /vault-sync/vault-sync.yaml
```

### Helm chart

```shell
helm repo add vault-sync https://pbchekin.github.io/vault-sync
helm search repo vault-sync
# create myvalues.yaml, using install/helm/values.yaml as the example
helm install vault-sync vault-sync/vault-sync -f myvalues.yaml
```

### Custom CA certificates

When running vault-sync locally, specify environment variable `SSL_CERT_FILE` pointing to a PEM file with a certificate or certificates (see https://docs.openssl.org/3.1/man7/openssl-env/).

When running vault-sync in Kubernetes, first create a Kubernetes secret from the PEM file.
For example:

```shell
kubectl --namespace=vault-sync create secret generic certs --from-file=ca-bundle.pem=/path/to/certificate.pem
```

Then specify the following Helm chart values:

```yaml
volumes:
  - name: certs
    secret:
      secretName: certs

volumeMounts:
  - mountPath: /certs
    name: certs
    readOnly: true

environmentVars:
  - name: SSL_CERT_FILE
    value: /certs/ca-bundle.pem
```
0707010000000A000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000001900000000vault-sync-0.11.0/docker0707010000000B000081A40000000000000000000000016815658100000199000000000000000000000000000000000000002400000000vault-sync-0.11.0/docker/DockerfileFROM rust:1.85.0 AS builder

WORKDIR /usr/src/vault-sync
COPY . .
RUN cargo install --path .

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/cargo/bin/vault-sync /usr/local/bin/vault-sync
# Smoke check that the image has all required libraries
RUN /usr/local/bin/vault-sync --version
CMD ["vault-sync"]
0707010000000C000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000001A00000000vault-sync-0.11.0/install0707010000000D000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000001F00000000vault-sync-0.11.0/install/helm0707010000000E000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000002A00000000vault-sync-0.11.0/install/helm/vault-sync0707010000000F000081A4000000000000000000000001681565810000015D000000000000000000000000000000000000003600000000vault-sync-0.11.0/install/helm/vault-sync/.helmignore# 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/
07070100000010000081A4000000000000000000000001681565810000044D000000000000000000000000000000000000003500000000vault-sync-0.11.0/install/helm/vault-sync/Chart.yamlapiVersion: v2
name: vault-sync
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.5

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 0.11.0
07070100000011000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000003400000000vault-sync-0.11.0/install/helm/vault-sync/templates07070100000012000081A40000000000000000000000016815658100000639000000000000000000000000000000000000003E00000000vault-sync-0.11.0/install/helm/vault-sync/templates/NOTES.txt1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
  {{- range .paths }}
  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
  {{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "vault-sync.fullname" . }})
  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "vault-sync.fullname" . }}'
  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "vault-sync.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
  echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "vault-sync.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
{{- end }}
07070100000013000081A40000000000000000000000016815658100000714000000000000000000000000000000000000004100000000vault-sync-0.11.0/install/helm/vault-sync/templates/_helpers.tpl{{/*
Expand the name of the chart.
*/}}
{{- define "vault-sync.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 "vault-sync.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 "vault-sync.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "vault-sync.labels" -}}
helm.sh/chart: {{ include "vault-sync.chart" . }}
{{ include "vault-sync.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "vault-sync.selectorLabels" -}}
app.kubernetes.io/name: {{ include "vault-sync.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "vault-sync.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "vault-sync.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
07070100000014000081A400000000000000000000000168156581000000E1000000000000000000000000000000000000004300000000vault-sync-0.11.0/install/helm/vault-sync/templates/configmap.yamlapiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "vault-sync.fullname" . }}
  labels:
    {{- include "vault-sync.labels" . | nindent 4 }}
data:
  vault-sync.yaml: |
    {{- toYaml .Values.vaultSync | nindent 4 }}
07070100000015000081A40000000000000000000000016815658100000AA4000000000000000000000000000000000000004400000000vault-sync-0.11.0/install/helm/vault-sync/templates/deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "vault-sync.fullname" . }}
  labels:
    {{- include "vault-sync.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
{{- end }}
  selector:
    matchLabels:
      {{- include "vault-sync.selectorLabels" . | nindent 6 }}
  template:
    metadata:
    {{- with .Values.podAnnotations }}
      annotations:
        {{- toYaml . | nindent 8 }}
    {{- end }}
      labels:
        {{- include "vault-sync.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "vault-sync.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 }}
          command: ["vault-sync", "--config", "/config/vault-sync.yaml"]
          volumeMounts:
            - name: config-volume
              mountPath: /config
            {{- with .Values.volumeMounts }}
            {{- toYaml . | nindent 12 }}
            {{- end }}
          {{- if .Values.vaultSync.bind }}
          ports:
            - name: tcp
              containerPort: {{ (split ":" .Values.vaultSync.bind)._1 }}
              protocol: TCP
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          env:
            - name: RUST_LOG
              value: info
          {{- $secretName := include "vault-sync.fullname" . }}
          {{- range $key, $value := .Values.secrets }}
            - name: {{ $key }}
              valueFrom:
                secretKeyRef:
                  key: {{ $key }}
                  name: {{ $secretName }}
          {{- end }}
          {{- with .Values.environmentVars }}
          {{- toYaml . | nindent 12 }}
          {{- end }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      volumes:
        - name: config-volume
          configMap:
            name: {{ include "vault-sync.fullname" . }}
        {{- with .Values.volumes }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
07070100000016000081A40000000000000000000000016815658100000395000000000000000000000000000000000000003D00000000vault-sync-0.11.0/install/helm/vault-sync/templates/hpa.yaml{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
  name: {{ include "vault-sync.fullname" . }}
  labels:
    {{- include "vault-sync.labels" . | nindent 4 }}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {{ include "vault-sync.fullname" . }}
  minReplicas: {{ .Values.autoscaling.minReplicas }}
  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
  metrics:
  {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
    - type: Resource
      resource:
        name: cpu
        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
  {{- end }}
  {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
    - type: Resource
      resource:
        name: memory
        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
  {{- end }}
{{- end }}
07070100000017000081A40000000000000000000000016815658100000422000000000000000000000000000000000000004100000000vault-sync-0.11.0/install/helm/vault-sync/templates/ingress.yaml{{- if .Values.ingress.enabled -}}
{{- $fullName := include "vault-sync.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
  name: {{ $fullName }}
  labels:
    {{- include "vault-sync.labels" . | nindent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- if .Values.ingress.tls }}
  tls:
    {{- range .Values.ingress.tls }}
    - hosts:
        {{- range .hosts }}
        - {{ . | quote }}
        {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ . }}
            backend:
              serviceName: {{ $fullName }}
              servicePort: {{ $svcPort }}
          {{- end }}
    {{- end }}
  {{- end }}
07070100000018000081A400000000000000000000000168156581000000D2000000000000000000000000000000000000004000000000vault-sync-0.11.0/install/helm/vault-sync/templates/secret.yamlapiVersion: v1
kind: Secret
metadata:
  name: {{ include "vault-sync.fullname" . }}
  labels:
    {{- include "vault-sync.labels" . | nindent 4 }}
type: Opaque
data:
  {{- toYaml .Values.secrets | nindent 2 }}
07070100000019000081A4000000000000000000000001681565810000019E000000000000000000000000000000000000004100000000vault-sync-0.11.0/install/helm/vault-sync/templates/service.yaml{{- if .Values.vaultSync.bind }}
apiVersion: v1
kind: Service
metadata:
  name: {{ include "vault-sync.fullname" . }}
  labels:
    {{- include "vault-sync.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "vault-sync.selectorLabels" . | nindent 4 }}
{{- end }}
0707010000001A000081A40000000000000000000000016815658100000146000000000000000000000000000000000000004800000000vault-sync-0.11.0/install/helm/vault-sync/templates/serviceaccount.yaml{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "vault-sync.serviceAccountName" . }}
  labels:
    {{- include "vault-sync.labels" . | nindent 4 }}
  {{- with .Values.serviceAccount.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
{{- end }}
0707010000001B000081A400000000000000000000000168156581000009CE000000000000000000000000000000000000003600000000vault-sync-0.11.0/install/helm/vault-sync/values.yaml# Default values for vault-sync.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

# See https://github.com/pbchekin/vault-sync/blob/main/vault-sync.example.yaml
vaultSync:
  id: vault-sync
  full_sync_interval: 3600
#  bind: 0.0.0.0:8202
  src:
    url: http://127.0.0.1:8200/
    prefix: ""
    backend: secret
#    token_ttl: 86400
#    token_max_ttl: 2764800
  dst:
    url: http://127.0.0.1:8200/
    prefix: ""
    backend: secret
#    token_ttl: 86400
#    token_max_ttl: 2764800

# Secrets must be base64 encoded
secrets:
  VAULT_SYNC_SRC_TOKEN: eHh4
#  VAULT_SYNC_SRC_ROLE_ID: xxx
#  VAULT_SYNC_SRC_SECRET_ID: xxx
  VAULT_SYNC_DST_TOKEN: eHh4
#  VAULT_SYNC_DST_ROLE_ID: xxx
#  VAULT_SYNC_DST_SECRET_ID: xxx

replicaCount: 1

image:
  repository: pbchekin/vault-sync
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: "0.9.2"

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: ""

podAnnotations: {}

podSecurityContext: {}
  # fsGroup: 2000

securityContext: {}
  # capabilities:
  #   drop:
  #   - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000

service:
  type: ClusterIP
  port: 8202

ingress:
  enabled: false
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: chart-example.local
      paths: []
  tls: []
  #  - secretName: chart-example-tls
  #    hosts:
  #      - chart-example.local

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: 128Mi

autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100
  targetCPUUtilizationPercentage: 80
  # targetMemoryUtilizationPercentage: 80

nodeSelector: {}

tolerations: []

affinity: {}

volumes: []

volumeMounts: []

environmentVars: []
0707010000001C000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000001A00000000vault-sync-0.11.0/scripts0707010000001D000081ED0000000000000000000000016815658100002F6E000000000000000000000000000000000000002200000000vault-sync-0.11.0/scripts/test.sh#!/bin/bash

# Local test.
# Requires installed vault.
# TODO: use vault container instead of locally installed vault.

set -e -o pipefail

vault server -dev -dev-root-token-id=unsafe-root-token &> vault.log &
echo $! > vault.pid

function cleanup() {(
  set -e
  if [[ -f vault.pid ]]; then
    kill $(<vault.pid) || true
    rm -f vault.pid
  fi
  if [[ -f vault-sync.pid ]]; then
    kill $(<vault-sync.pid) || true
    rm -f vault-sync.pid
  fi
)}

trap cleanup EXIT

export VAULT_ADDR='http://127.0.0.1:8200'

# Make sure Vault is running
while ! vault token lookup; do sleep 1; done

# Enable AppRole auth method
vault auth enable approle

vault secrets enable -version=1 -path=secret1 kv
vault secrets enable -version=2 -path=secret2 kv
vault secrets enable -version=2 -path=secret11 kv
vault secrets enable -version=2 -path=secret12 kv
vault secrets enable -version=2 -path=secret21 kv
vault secrets enable -version=2 -path=secret22 kv

vault secrets list

# Check all secret engines are enabled
vault secrets list | grep -qE '^secret/\s+kv'
vault secrets list | grep -qE '^secret1/\s+kv'
vault secrets list | grep -qE '^secret2/\s+kv'
vault secrets list | grep -qE '^secret11/\s+kv'
vault secrets list | grep -qE '^secret12/\s+kv'
vault secrets list | grep -qE '^secret21/\s+kv'
vault secrets list | grep -qE '^secret22/\s+kv'

# Create reader policy
cat <<EOF | vault policy write vault-sync-reader -
  # Default secret backend "secret" (kv version 2)
  path "secret/data/*" {
    capabilities = ["read", "list"]
  }
  path "secret/metadata/*" {
    capabilities = ["read", "list"]
  }

  # Custom secret backend "secret1" (kv version 1)
  path "secret1/*" {
    capabilities = ["read", "list"]
  }

  # Custom secret backend "secret2" (kv version 2)
  path "secret2/data/*" {
    capabilities = ["read", "list"]
  }
  path "secret2/metadata/*" {
    capabilities = ["read", "list"]
  }
EOF

# Create writer policy
cat <<EOF | vault policy write vault-sync-writer -
  # Default secret backend "secret" (kv version 2)
  path "secret/data/*" {
    capabilities = ["create", "read", "update", "delete"]
  }

  # Custom secret backend "secret1" (kv version 1)
  path "secret1/*" {
    capabilities = ["create", "read", "update", "delete"]
  }

  # Custom secret backend "secret2" (kv version 2)
  path "secret2/data/*" {
    capabilities = ["create", "read", "update", "delete"]
  }
EOF

# Create new AppRoles
vault write auth/approle/role/vault-sync-reader token_policies=vault-sync-reader
vault write auth/approle/role/vault-sync-writer token_policies=vault-sync-writer

cat <<EOF > /tmp/vault-sync-token.env
export VAULT_SYNC_SRC_TOKEN=unsafe-root-token
export VAULT_SYNC_DST_TOKEN=unsafe-root-token
EOF

cat <<EOF > /tmp/vault-sync-app-role.env
export VAULT_SYNC_SRC_ROLE_ID=$(vault read auth/approle/role/vault-sync-reader/role-id -format=json | jq -r .data.role_id)
export VAULT_SYNC_SRC_SECRET_ID=$(vault write -f auth/approle/role/vault-sync-reader/secret-id -format=json | jq -r .data.secret_id)
export VAULT_SYNC_DST_ROLE_ID=$(vault read auth/approle/role/vault-sync-writer/role-id -format=json | jq -r .data.role_id)
export VAULT_SYNC_DST_SECRET_ID=$(vault write -f auth/approle/role/vault-sync-writer/secret-id -format=json | jq -r .data.secret_id)
EOF

function test_token {(
  local src_backend=$1
  local dst_backend=${2:-$src_backend}
  local secret_name=test-$RANDOM

  vault kv put -mount $src_backend ${src_prefix}${secret_name} foo=bar

  source /tmp/vault-sync-token.env
  cargo run -- --config /tmp/vault-sync.yaml --once
  vault kv get -mount $dst_backend ${dst_prefix}${secret_name}
  vault kv get -mount $dst_backend ${dst_prefix}${secret_name} | grep -qE '^foo\s+bar$'
)}

function test_app_role {(
  local src_backend=$1
  local dst_backend=${2:-$src_backend}
  local secret_name=test-$RANDOM

  vault kv put -mount $src_backend ${src_prefix}${secret_name} foo=bar

  source /tmp/vault-sync-app-role.env
  cargo run -- --config /tmp/vault-sync.yaml --once
  vault kv get -mount $dst_backend ${dst_prefix}${secret_name}
  vault kv get -mount $dst_backend ${dst_prefix}${secret_name} | grep -qE '^foo\s+bar$'
)}

function test_token_with_audit_device {(
  local src_backend=$1
  local dst_backend=${2:-$src_backend}
  local secret_name=test-$RANDOM
  local audit_device_name=vault-sync-$src_backend-$dst_backend

  source /tmp/vault-sync-token.env

  vault kv put -mount $src_backend ${src_prefix}${secret_name}-1 foo=bar

  cargo run -- --config /tmp/vault-sync.yaml &
  echo $! > vault-sync.pid

  echo Wating for vault-sync to start and make the initial sync ...
  VAULT_SYNC_READY=""
  for i in 1 2 3 4 5; do
    if vault kv get -mount $dst_backend ${dst_prefix}${secret_name}-1 2> /dev/null; then
      vault kv get -mount $dst_backend ${dst_prefix}${secret_name}-1 | grep -qE '^foo\s+bar$'
      VAULT_SYNC_READY="true"
      break
    fi
    sleep 1
  done
  if [[ ! $VAULT_SYNC_READY ]]; then
    echo "vault-sync failed to start with audit device"
    exit 1
  fi

  # Enable audit device that sends log to vault-sync
  vault audit enable -path $audit_device_name socket socket_type=tcp address=127.0.0.1:8202

  vault kv put -mount $src_backend ${dst_prefix}${secret_name}-2 foo=bar

  echo Wating for vault-sync to sync on event ...
  VAULT_SYNC_READY=""
  for i in 1 2 3 4 5; do
    if vault kv get -mount $dst_backend ${dst_prefix}/${secret_name}-2 2> /dev/null; then
      vault kv get -mount $dst_backend ${dst_prefix}/${secret_name}-2 | grep -qE '^foo\s+bar$'
      VAULT_SYNC_READY="true"
      break
    fi
    sleep 1
  done
  if [[ ! $VAULT_SYNC_READY ]]; then
    echo "vault-sync failed to sync on the event from the audit device"
    exit 1
  fi

  vault audit disable $audit_device_name

  kill $(<vault-sync.pid)
  rm vault-sync.pid
)}

function test_multiple_backends {(
  local secret_name=test-$RANDOM
  local audit_device_name=vault-sync

  source /tmp/vault-sync-token.env

  vault kv put -mount secret11 ${secret_name}-1 foo=bar
  vault kv put -mount secret12 ${secret_name}-1 foo=bar

  cargo run -- --config /tmp/vault-sync.yaml &
  echo $! > vault-sync.pid

  echo Wating for vault-sync to start and make the initial sync ...
  VAULT_SYNC_READY1=""
  VAULT_SYNC_READY2=""
  for i in 1 2 3 4 5; do
    if vault kv get -mount secret21 ${secret_name}-1 &> /dev/null; then
      vault  kv get -mount secret21 ${secret_name}-1 | grep -qE '^foo\s+bar$'
      VAULT_SYNC_READY1="true"
    fi
    if vault kv get -mount secret22 ${secret_name}-1 &> /dev/null; then
      vault  kv get -mount secret22 ${secret_name}-1 | grep -qE '^foo\s+bar$'
      VAULT_SYNC_READY2="true"
    fi
    sleep 1
  done
  if [[ ! $VAULT_SYNC_READY1 || ! $VAULT_SYNC_READY1 ]]; then
    echo "vault-sync failed to start with audit device"
    exit 1
  fi

  # Enable audit device that sends log to vault-sync
  vault audit enable -path $audit_device_name socket socket_type=tcp address=127.0.0.1:8202

  vault kv put -mount secret11 ${secret_name}-2 foo=bar
  vault kv put -mount secret12 ${secret_name}-2 foo=bar

  echo Wating for vault-sync to sync on event ...
  VAULT_SYNC_READY1=""
  VAULT_SYNC_READY2=""
  for i in 1 2 3 4 5; do
    if vault kv get -mount secret21 ${secret_name}-2 &> /dev/null; then
      vault  kv get -mount secret21 ${secret_name}-2 | grep -qE '^foo\s+bar$'
      VAULT_SYNC_READY1="true"
    fi
    if vault kv get -mount secret22 ${secret_name}-2 &> /dev/null; then
      vault  kv get -mount secret22 ${secret_name}-2 | grep -qE '^foo\s+bar$'
      VAULT_SYNC_READY2="true"
    fi
    sleep 1
  done
  if [[ ! $VAULT_SYNC_READY1 || ! $VAULT_SYNC_READY2 ]]; then
    echo "vault-sync failed to sync on the event from the audit device"
    exit 1
  fi

  vault audit disable $audit_device_name

  kill $(<vault-sync.pid)
  rm vault-sync.pid
)}

# secret/src -> secret/dst
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync
full_sync_interval: 10
src:
  url: http://127.0.0.1:8200/
  prefix: src
dst:
  url: http://127.0.0.1:8200/
  prefix: dst
EOF

src_prefix="src/"
dst_prefix="dst/"

test_token secret
test_app_role secret

# secret1/src -> secret1/dst
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync
full_sync_interval: 10
src:
  url: http://127.0.0.1:8200/
  prefix: src
  backend: secret1
  version: 1
dst:
  url: http://127.0.0.1:8200/
  prefix: dst
  backend: secret1
  version: 1
EOF

src_prefix="src/"
dst_prefix="dst/"

test_token secret1
test_app_role secret1

# secret2/src -> secret2/dst
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync
full_sync_interval: 10
src:
  url: http://127.0.0.1:8200/
  prefix: src
  backend: secret2
dst:
  url: http://127.0.0.1:8200/
  prefix: dst
  backend: secret2
EOF

src_prefix="src/"
dst_prefix="dst/"

test_token secret2
test_app_role secret2

# secret1/src -> secret2/dst
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync
full_sync_interval: 10
src:
  url: http://127.0.0.1:8200/
  prefix: src
  backend: secret1
  version: 1
dst:
  url: http://127.0.0.1:8200/
  prefix: dst
  backend: secret2
EOF

src_prefix="src/"
dst_prefix="dst/"

test_token secret1 secret2
test_app_role secret1 secret2

# secret2/src -> secret1/dst
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync
full_sync_interval: 10
src:
  url: http://127.0.0.1:8200/
  prefix: src
  backend: secret2
dst:
  url: http://127.0.0.1:8200/
  prefix: dst
  backend: secret1
  version: 1
EOF

src_prefix="src/"
dst_prefix="dst/"

test_token secret2 secret1
test_app_role secret2 secret1

# secret1 -> secret2
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync
full_sync_interval: 10
src:
  url: http://127.0.0.1:8200/
  backend: secret1
  version: 1
dst:
  url: http://127.0.0.1:8200/
  backend: secret2
EOF

src_prefix=""
dst_prefix=""

test_token secret1 secret2
test_app_role secret1 secret2

# secret2 -> secret1
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync
full_sync_interval: 10
src:
  url: http://127.0.0.1:8200/
  backend: secret2
dst:
  url: http://127.0.0.1:8200/
  backend: secret1
  version: 1
EOF

src_prefix=""
dst_prefix=""

test_token secret2 secret1
test_app_role secret2 secret1

# Enable audit device that always works
vault audit enable -path vault-audit file file_path=vault-audit.log

# secret/src -> secret/dst
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync-secret
bind: 0.0.0.0:8202
full_sync_interval: 60
src:
  url: http://127.0.0.1:8200/
  prefix: src
dst:
  url: http://127.0.0.1:8200/
  prefix: dst
EOF

src_prefix="src/"
dst_prefix="dst/"

test_token_with_audit_device secret

# secret1/src -> secret1/dst
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync-secret
full_sync_interval: 60
bind: 0.0.0.0:8202
src:
  url: http://127.0.0.1:8200/
  prefix: src
  backend: secret1
  version: 1
dst:
  url: http://127.0.0.1:8200/
  prefix: dst
  backend: secret1
  version: 1
EOF

src_prefix="src/"
dst_prefix="dst/"

test_token_with_audit_device secret1

# secret2/src -> secret2/dst
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync-secret
full_sync_interval: 60
bind: 0.0.0.0:8202
src:
  url: http://127.0.0.1:8200/
  prefix: src
  backend: secret2
dst:
  url: http://127.0.0.1:8200/
  prefix: dst
  backend: secret2
EOF

src_prefix="src/"
dst_prefix="dst/"

test_token_with_audit_device secret2

# secret1 -> secret2
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync-secret
full_sync_interval: 60
bind: 0.0.0.0:8202
src:
  url: http://127.0.0.1:8200/
  backend: secret1
  version: 1
dst:
  url: http://127.0.0.1:8200/
  backend: secret2
EOF

src_prefix=""
dst_prefix=""

test_token_with_audit_device secret1 secret2

# secret2 -> secret1
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync-secret
full_sync_interval: 60
bind: 0.0.0.0:8202
src:
  url: http://127.0.0.1:8200/
  backend: secret2
dst:
  url: http://127.0.0.1:8200/
  backend: secret1
  version: 1
EOF

src_prefix=""
dst_prefix=""

test_token_with_audit_device secret2 secret1

# secret11, secret12 -> secret21, secret22
cat <<EOF > /tmp/vault-sync.yaml
id: vault-sync-secret
full_sync_interval: 60
bind: 0.0.0.0:8202
src:
  url: http://127.0.0.1:8200/
  backends:
    - secret11
    - secret12
dst:
  url: http://127.0.0.1:8200/
  backends:
    - secret21
    - secret22
EOF

test_multiple_backends
0707010000001E000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000001600000000vault-sync-0.11.0/src0707010000001F000081A40000000000000000000000016815658100000287000000000000000000000000000000000000001F00000000vault-sync-0.11.0/src/audit.rsuse serde::{Deserialize, Serialize};

#[derive(Deserialize, Debug)]
pub struct AuditLog {
    #[allow(dead_code)]
    pub time: String,
    #[serde(rename = "type")]
    pub log_type: String,
    pub request: Request,
}

#[derive(Deserialize, Debug)]
pub struct Request {
    pub operation: String,
    pub mount_type: Option<String>,
    pub path: String,
}

#[derive(Serialize, Debug)]
pub struct CreateAuditDeviceRequest {
    #[serde(rename = "type")]
    pub audit_device_type: String,
    pub options: AuditDeviceOptions,
}

#[derive(Serialize, Debug)]
pub struct AuditDeviceOptions {
    pub address: String,
    pub socket_type: String,
}
07070100000020000081A4000000000000000000000001681565810000295A000000000000000000000000000000000000002000000000vault-sync-0.11.0/src/config.rsuse std::env;
use std::error::Error;
use std::fmt;
use std::fmt::Formatter;
use std::fs::File;

use serde::{Deserialize, Serialize, Serializer};
use serde_repr::*;

#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum VaultAuthMethod {
    TokenAuth {
        #[serde(serialize_with = "sanitize")]
        token: String,
    },
    AppRoleAuth {
        #[serde(serialize_with = "sanitize")]
        role_id: String,
        #[serde(serialize_with = "sanitize")]
        secret_id: String,
    }
}

#[derive(Serialize_repr, Deserialize_repr, PartialEq, Clone, Debug)]
#[repr(u8)]
pub enum EngineVersion {
    V1 = 1,
    V2 = 2,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VaultHost {
    pub url: String,
    #[serde(flatten)]
    pub auth: Option<VaultAuthMethod>,
    pub token_ttl: Option<u64>,
    pub token_max_ttl: Option<u64>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Backend {
    #[serde(rename = "backend")]
    Backend(String),
    #[serde(rename = "backends")]
    Backends(Vec<String>),
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VaultSource {
    #[serde(flatten)]
    pub host: VaultHost,
    #[serde(default)]
    pub prefix: String,
    #[serde(flatten)]
    pub backend: Option<Backend>,
    #[serde(default)]
    pub version: EngineVersion,
    pub namespace: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VaultDestination {
    #[serde(flatten)]
    pub host: VaultHost,
    #[serde(default)]
    pub prefix: String,
    #[serde(flatten)]
    pub backend: Option<Backend>,
    #[serde(default)]
    pub version: EngineVersion,
    pub namespace: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VaultSyncConfig {
    pub id: String,
    pub full_sync_interval: u64,
    pub bind: Option<String>,
    pub src: VaultSource,
    pub dst: VaultDestination,
}

#[derive(Debug, Clone)]
pub enum ConfigError {
    AuthRequired,
    OneToManyNotSupported,
    ManyToOneNotSupported,
    DifferentNumberOfBackends,
}

// Returns backend or backends as a vector.
pub fn get_backends(backend: &Option<Backend>) -> Vec<String> {
    match backend {
        Some(Backend::Backend(backend)) => vec![backend.into()],
        Some(Backend::Backends(backends)) => backends.clone(),
        _ => panic!("Not implemented"),
    }
}

impl Default for EngineVersion {
    fn default() -> Self {
        EngineVersion::V2
    }
}

impl VaultSyncConfig {
    pub fn from_file(file_name: &str) -> Result<VaultSyncConfig, Box<dyn Error>> {
        let file = File::open(file_name)?;
        let mut config: VaultSyncConfig = serde_yaml::from_reader(file)?;
        config.auth_from_env()?;
        config.defaults()?;
        config.validate()?;
        Ok(config)
    }

    fn auth_from_env(&mut self) -> Result<(), Box<dyn Error>> {
        if self.src.host.auth.is_none() {
            self.src.host.auth = Some(VaultAuthMethod::from_env("VAULT_SYNC_SRC")?);
        }
        if self.dst.host.auth.is_none() {
            self.dst.host.auth = Some(VaultAuthMethod::from_env("VAULT_SYNC_DST")?);
        }
        Ok(())
    }

    fn defaults(&mut self) -> Result<(), Box<dyn Error>> {
        if self.src.backend.is_none() {
            self.src.backend = Some(Backend::Backend("secret".into()));
        }
        if self.dst.backend.is_none() {
            self.dst.backend = self.src.backend.clone();
        }
        Ok(())
    }

    fn validate(&self) -> Result<(), Box<dyn Error>> {
        let src_backend = self.src.backend.as_ref().unwrap();
        let dst_backend = self.dst.backend.as_ref().unwrap();

        match &src_backend {
            Backend::Backend(_) => match &dst_backend {
                Backend::Backends(_) => {
                    return Err(ConfigError::OneToManyNotSupported.into());
                },
                _ => {},
            },
            Backend::Backends(src_backends) => match &dst_backend {
                Backend::Backend(_) => {
                    return Err(ConfigError::ManyToOneNotSupported.into());
                },
                Backend::Backends(dst_backends) => {
                    if src_backends.len() != dst_backends.len() {
                        return Err(ConfigError::DifferentNumberOfBackends.into());
                    }
                }
            }
        }
        Ok(())
    }
}

impl VaultAuthMethod {
    fn from_env(prefix: &str) -> Result<VaultAuthMethod, Box<dyn Error>> {
        let token = env::var(format!("{}_TOKEN", prefix));
        let role_id = env::var(format!("{}_ROLE_ID", prefix));
        let secret_id = env::var(format!("{}_SECRET_ID", prefix));
        if let Ok(token) = token {
            return Ok(VaultAuthMethod::TokenAuth { token })
        }
        if let (Ok(role_id), Ok(secret_id)) = (role_id, secret_id) {
            return Ok(VaultAuthMethod::AppRoleAuth { role_id, secret_id })
        }
        Err(ConfigError::AuthRequired.into())
    }
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            ConfigError::AuthRequired =>
                write!(f, "Vault token or both app role id and secret id are required"),
            ConfigError::OneToManyNotSupported =>
                write!(f, "Syncing one backend to many not supported"),
            ConfigError::ManyToOneNotSupported =>
                write!(f, "Syncing many backends to one not supported"),
            ConfigError::DifferentNumberOfBackends =>
                write!(f, "Different number of backends for source and destination"),
        }
    }
}

impl Error for ConfigError {
}

fn sanitize<S>(_: &str, s: S) -> Result<S::Ok, S::Error>
where
    S: Serializer,
{
    s.serialize_str("***")
}

#[cfg(test)]
mod tests {
    use std::error::Error;
    use crate::config::{EngineVersion, VaultSyncConfig, get_backends, ConfigError};

    #[test]
    fn test_load() -> Result<(), Box<dyn Error>> {
        let yaml = r#"
            id: vault-sync-id
            full_sync_interval: 60
            bind: 0.0.0.0:8202
            src:
              url: http://127.0.0.1:8200/
              prefix: src
            dst:
              url: http://127.0.0.1:8200/
              prefix: dst
              version: 1
        "#;
        let mut config: VaultSyncConfig = serde_yaml::from_str(yaml)?;
        config.defaults()?;
        assert_eq!(config.id, "vault-sync-id");
        assert_eq!(config.bind, Some("0.0.0.0:8202".to_string()));
        assert_eq!(config.src.version, EngineVersion::V2);
        assert_eq!(config.dst.version, EngineVersion::V1);
        Ok(())
    }

    fn render_yaml(
        src: Option<&str>,
        dst: Option<&str>,
        src_key: &str,
        dst_key: &str,
    ) -> String {
        format!(
            r#"
                id: vault-sync-id
                full_sync_interval: 60
                src:
                  url: http://127.0.0.1:8200/
                  {}
                dst:
                  url: http://127.0.0.1:8200/
                  {}
            "#,
            src.map_or("".to_string(), |v| format!("{}: {}", src_key, v)),
            dst.map_or("".to_string(), |v| format!("{}: {}", dst_key, v)),
        )
    }

    fn render_backend_yaml(src: Option<&str>, dst: Option<&str>) -> String {
        render_yaml(src, dst, "backend", "backend")
    }

    fn render_backends_yaml(src: Option<&str>, dst: Option<&str>) -> String {
        render_yaml(src, dst, "backends", "backends")
    }

    fn test_single_backend(
        src: Option<&str>,
        dst: Option<&str>,
        expected_src: &str,
        expected_dst: &str,
    ) -> Result<(), Box<dyn Error>> {
        let yaml = render_backend_yaml(src, dst);
        let mut config: VaultSyncConfig = serde_yaml::from_str(&yaml)?;
        config.defaults()?;
        config.validate()?;
        assert_eq!(get_backends(&config.src.backend).first().unwrap(), expected_src);
        assert_eq!(get_backends(&config.dst.backend).first().unwrap(), expected_dst);
        Ok(())
    }

    fn test_many_backends(
        src: Option<&str>,
        dst: Option<&str>,
        expected_src: &[&str],
        expected_dst: &[&str],
    ) -> Result<(), Box<dyn Error>> {
        let yaml = render_backends_yaml(src, dst);
        let mut config: VaultSyncConfig = serde_yaml::from_str(&yaml)?;
        config.defaults()?;
        config.validate()?;
        assert_eq!(get_backends(&config.src.backend), expected_src);
        assert_eq!(get_backends(&config.dst.backend), expected_dst);
        Ok(())
    }

    #[test]
    fn test_backends() -> Result<(), Box<dyn Error>> {
        test_single_backend(None, None, "secret", "secret")?;
        test_single_backend(Some("custom"), None, "custom", "custom")?;
        test_single_backend(None, Some("custom"), "secret", "custom")?;
        test_single_backend(Some("src"), Some("dst"), "src", "dst")?;
        test_many_backends(Some("[foo, baz]"), None, &["foo", "baz"], &["foo", "baz"])?;
        test_many_backends(Some("[foo, baz]"), Some("[bar, qux]"), &["foo", "baz"], &["bar", "qux"])?;
        Ok(())
    }

    #[test]
    fn test_one_to_many_error() -> Result<(), Box<dyn Error>> {
        let yaml = render_yaml(Some(""), Some("[foo, baz]"), "backend", "backends");
        let mut config: VaultSyncConfig = serde_yaml::from_str(&yaml)?;
        config.defaults()?;
        let result = config.validate();
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), ConfigError::OneToManyNotSupported.to_string());
        Ok(())
    }

    #[test]
    fn test_many_to_one_error() -> Result<(), Box<dyn Error>> {
        let yaml = render_yaml(Some("[foo, baz]"), Some("bar"), "backends", "backend");
        let mut config: VaultSyncConfig = serde_yaml::from_str(&yaml)?;
        config.defaults()?;
        let result = config.validate();
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), ConfigError::ManyToOneNotSupported.to_string());
        Ok(())
    }

    #[test]
    fn test_different_numbers_of_backend() -> Result<(), Box<dyn Error>> {
        let yaml = render_yaml(Some("[foo]"), Some("[baz, bar]"), "backends", "backends");
        let mut config: VaultSyncConfig = serde_yaml::from_str(&yaml)?;
        config.defaults()?;
        let result = config.validate();
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), ConfigError::DifferentNumberOfBackends.to_string());
        Ok(())
    }
}07070100000021000081A4000000000000000000000001681565810000163A000000000000000000000000000000000000001E00000000vault-sync-0.11.0/src/main.rsuse std::{thread};
use std::error::Error;
use std::net::TcpListener;
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
use std::thread::JoinHandle;

use clap::{crate_authors, crate_version, Arg, App};
use log::{error, info};
use simplelog::*;

use config::{VaultHost, VaultSyncConfig};
use vault::VaultClient;
use crate::config::{EngineVersion, get_backends};

mod audit;
mod config;
mod sync;
mod vault;

fn main() -> Result<(), Box<dyn Error>> {
    TermLogger::init(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto)?;

    let matches = App::new("vault-sync")
        .author(crate_authors!())
        .version(crate_version!())
        .arg(Arg::with_name("config")
            .long("config")
            .value_name("FILE")
            .help("Configuration file")
            .default_value("./vault-sync.yaml")
            .takes_value(true))
        .arg(Arg::with_name("dry-run")
            .long("dry-run")
            .help("Do not do any changes with the destination Vault"))
        .arg(Arg::with_name("once")
            .long("once")
            .help("Run the full sync once, then exit"))
        .get_matches();

    let config = load_config(matches.value_of("config").unwrap())?;
    let (tx, rx): (mpsc::Sender<sync::SecretOp>, mpsc::Receiver<sync::SecretOp>) = mpsc::channel();

    let log_sync = match &config.bind {
        Some(_) => Some(log_sync_worker(&config, tx.clone())?),
        None => None,
    };
    info!("Connecting to {}", &config.src.host.url);
    let src_client = vault_client(&config.src.host, &config.src.version, config.src.namespace.clone())?;
    let shared_src_client = Arc::new(Mutex::new(src_client));
    let src_token = token_worker(&config.src.host, &config.src.version, shared_src_client.clone(), config.src.namespace.clone());

    info!("Connecting to {}", &config.dst.host.url);
    let dst_client = vault_client(&config.dst.host, &config.dst.version, config.dst.namespace.clone())?;
    let shared_dst_client = Arc::new(Mutex::new(dst_client));
    let dst_token = token_worker(&config.dst.host, &config.dst.version, shared_dst_client.clone(), config.dst.namespace.clone());

    info!(
        "Audit device {} exists: {}",
        &config.id,
        sync::audit_device_exists(&config.id, shared_src_client.clone()),
    );

    let sync = sync_worker(
        rx,
        &config,
        shared_src_client.clone(),
        shared_dst_client.clone(),
        matches.is_present("dry-run"),
        matches.is_present("once"),
    );

    let mut join_handlers = vec![sync];

    if !matches.is_present("once") {
        let full_sync = full_sync_worker(&config, shared_src_client.clone(), tx.clone());
        join_handlers.push(full_sync);
        join_handlers.push(src_token);
        join_handlers.push(dst_token);
        if log_sync.is_some() {
            join_handlers.push(log_sync.unwrap());
        }
    } else {
        let backends = get_backends(&config.src.backend);
        sync::full_sync(&config.src.prefix, &backends, shared_src_client.clone(), tx.clone());
    };

    // Join all threads
    for handler in join_handlers {
        let _ = handler.join();
    }

    Ok(())
}

fn load_config(file_name: &str) -> Result<VaultSyncConfig, Box<dyn Error>> {
    match VaultSyncConfig::from_file(file_name) {
        Ok(config) => {
            info!("Configuration from {}:\n{}", file_name, serde_json::to_string_pretty(&config).unwrap());
            Ok(config)
        },
        Err(error) => {
            error!("Failed to load configuration file {}: {}", file_name, error);
            Err(error)
        }
    }
}

fn vault_client(host: &VaultHost, version: &EngineVersion, namespace: Option<String>) -> Result<VaultClient, Box<dyn Error>> {
    match vault::vault_client(host, version, namespace) {
        Ok(client) => {
            Ok(client)
        },
        Err(error) => {
            error!("Failed to connect to {}: {}", &host.url, error);
            Err(error.into())
        }
    }
}

fn token_worker(host: &VaultHost, version: &EngineVersion, client: Arc<Mutex<VaultClient>>, namespace: Option<String>) -> JoinHandle<()> {
    let host = host.clone();
    let version = version.clone();
    thread::spawn(move || {
        vault::token_worker(&host, &version, client, namespace.clone());
    })
}

fn sync_worker(
    rx: mpsc::Receiver<sync::SecretOp>,
    config: &VaultSyncConfig,
    src_client: Arc<Mutex<VaultClient>>,
    dst_client: Arc<Mutex<VaultClient>>,
    dry_run: bool,
    run_once: bool,
) -> thread::JoinHandle<()> {
    info!("Dry run: {}", dry_run);
    let config = config.clone();
    thread::spawn(move || {
        sync::sync_worker(rx, &config, src_client, dst_client, dry_run, run_once);
    })
}

fn log_sync_worker(config: &VaultSyncConfig, tx: mpsc::Sender<sync::SecretOp>) -> Result<JoinHandle<()>, std::io::Error> {
    let addr = &config.bind.clone().unwrap();
    let config = config.clone();
    info!("Listening on {}", addr);
    let listener = TcpListener::bind(addr)?;
    let handle = thread::spawn(move || {
        for stream in listener.incoming() {
            if let Ok(stream) = stream {
                let tx = tx.clone();
                let config = config.clone();
                thread::spawn(move || {
                    sync::log_sync(&config, stream, tx);
                });
            }
        }
    });
    Ok(handle)
}

fn full_sync_worker(
    config: &VaultSyncConfig,
    client: Arc<Mutex<VaultClient>>,
    tx: mpsc::Sender<sync::SecretOp>
) -> thread::JoinHandle<()>{
    let config = config.clone();
    thread::spawn(move || {
        sync::full_sync_worker(&config, client, tx);
    })
}
07070100000022000081A40000000000000000000000016815658100003791000000000000000000000000000000000000001E00000000vault-sync-0.11.0/src/sync.rsuse std::{thread, time};
use std::collections::HashMap;
use std::io::{BufRead, BufReader};
use std::net::TcpStream;
use std::sync::{Arc, Mutex};
use std::sync::mpsc;

use hashicorp_vault::client::{EndpointResponse, HttpVerb};
use log::{debug, info, warn};
use serde_json::Value;

use crate::audit;
use crate::config::{EngineVersion, get_backends, VaultSyncConfig};
use crate::vault::VaultClient;

pub fn audit_device_exists(name: &str, client: Arc<Mutex<VaultClient>>) -> bool {
    let client = client.lock().unwrap();
    let name = format!("{}/", name);
    match client.call_endpoint::<Value>(HttpVerb::GET, "sys/audit", None, None) {
        Ok(response) => {
            debug!("GET sys/audit: {:?}", response);
            if let EndpointResponse::VaultResponse(response) = response {
                if let Some(Value::Object(map)) = response.data {
                    for (key, _) in &map {
                        if key == &name {
                            return true;
                        }
                    }
                }
            }
        },
        Err(error) => {
            warn!("GET sys/audit: {}", error);
        }
    }
    false
}

pub fn full_sync_worker(
    config: &VaultSyncConfig,
    client: Arc<Mutex<VaultClient>>,
    tx: mpsc::Sender<SecretOp>
) {
    info!("FullSync worker started");
    let interval = time::Duration::from_secs(config.full_sync_interval);
    let prefix = &config.src.prefix;
    let backends = get_backends(&config.src.backend);
    loop {
        full_sync(prefix, &backends, client.clone(), tx.clone());
        thread::sleep(interval);
    }
}

struct Item {
    parent: String,
    secrets: Option<Vec<String>>,
    index: usize,
}

pub fn full_sync(prefix: &str, backends: &Vec<String>, client: Arc<Mutex<VaultClient>>, tx: mpsc::Sender<SecretOp>) {
    let prefix= normalize_prefix(prefix);
    info!("FullSync started");
    let now = time::Instant::now();
    for backend in backends {
        full_sync_internal(&prefix, backend, client.clone(), tx.clone());
    }
    info!("FullSync finished in {}ms", now.elapsed().as_millis());
}

fn full_sync_internal(prefix: &str, backend: &str, client: Arc<Mutex<VaultClient>>, tx: mpsc::Sender<SecretOp>) {
    let mut stack: Vec<Item> = Vec::new();
    let item = Item {
        parent: prefix.to_string(),
        secrets: None,
        index: 0,
    };
    stack.push(item);

    'outer: while stack.len() > 0 {
        let len = stack.len();
        let item = stack.get_mut(len - 1).unwrap();
        if item.secrets.is_none() {
            let secrets = {
                let mut client = client.lock().unwrap();
                client.secret_backend(backend);
                client.list_secrets(&item.parent)
            };
            match secrets {
                Ok(secrets) => {
                    item.secrets = Some(secrets);
                },
                Err(error) => {
                    warn!("Failed to list secrets in {}: {}", &item.parent, error);
                }
            }
        }
        if let Some(secrets) = &item.secrets {
            while item.index < secrets.len() {
                let secret = &secrets[item.index];
                item.index += 1;
                if secret.ends_with("/") {
                    let item = Item {
                        parent: format!("{}{}", &item.parent, secret),
                        secrets: None,
                        index: 0,
                    };
                    stack.push(item);
                    continue 'outer;
                } else {
                    let full_name = format!("{}{}", &item.parent, &secret);
                    let op = SecretOp::Create(SecretPath { mount: backend.to_string(), path: full_name });
                    if let Err(error) = tx.send(op) {
                        warn!("Failed to send a secret to a sync thread: {}", error);
                    }
                }
            }
        }
        stack.pop();
    }
    let _ = tx.send(SecretOp::FullSyncFinished);
}

pub fn log_sync(config: &VaultSyncConfig, stream: TcpStream, tx: mpsc::Sender<SecretOp>) {
    match stream.peer_addr() {
        Ok(peer_addr) => {
            info!("New connection from {}", peer_addr);
        },
        Err(_) => {
            info!("New connection");
        }
    }
    let backends = get_backends(&config.src.backend);
    let prefix = &config.src.prefix;
    let version = &config.src.version;

    let mut reader = BufReader::new(stream);
    loop {
        let mut line = String::new();
        match reader.read_line(&mut line) {
            Ok(0) => {
                // EOF
                break;
            },
            Ok(_) => {
                debug!("Log: '{}'", line.trim());
                let audit_log: Result<audit::AuditLog, _> = serde_json::from_str(&line);
                match audit_log {
                    Ok(audit_log) => {
                        if let Some(op) = audit_log_op(&backends, &prefix, &version, &audit_log) {
                            if let Err(error) = tx.send(op) {
                                warn!("Failed to send a secret to a sync thread: {}", error);
                            }
                        }
                    },
                    Err(error) => {
                        warn!("Failed to deserialize: {}, response: {}", error, &line);
                    }
                }
            },
            Err(error) => {
                warn!("Error: {}", error);
                break;
            }
        }
    }
    debug!("Closed connection");
}

#[derive(Debug)]
pub struct SecretPath{
    mount: String,
    path: String,
}

#[derive(Debug)]
pub enum SecretOp {
    Create(SecretPath),
    Update(SecretPath),
    Delete(SecretPath),
    FullSyncFinished,
}

struct SyncStats {
    updated: u64,
    deleted: u64,
}

impl SyncStats {
    fn new() -> SyncStats {
        SyncStats { updated: 0, deleted: 0 }
    }
    fn reset(&mut self) {
        self.updated = 0;
        self.deleted = 0;
    }
}

pub fn sync_worker(
    rx: mpsc::Receiver<SecretOp>,
    config: &VaultSyncConfig,
    src_client: Arc<Mutex<VaultClient>>,
    dst_client: Arc<Mutex<VaultClient>>,
    dry_run: bool,
    run_once: bool,
) {
    let src_prefix = normalize_prefix(&config.src.prefix);
    let dst_prefix = normalize_prefix(&config.dst.prefix);
    let src_mounts = get_backends(&config.src.backend);
    let dst_mounts = get_backends(&config.dst.backend);
    let mount_map: HashMap<&str, &str> = src_mounts.iter().map(|s| s.as_str()).zip(dst_mounts.iter().map(|s| s.as_str())).collect();
    info!("Sync worker started");
    let mut stats = SyncStats::new();
    loop {
        let op = rx.recv();
        if let Ok(op) = op {
            match op {
                SecretOp::Update(path) | SecretOp::Create(path) => {
                    let src_path = &path.path;
                    let dst_path = secret_src_to_dst_path(&src_prefix, &dst_prefix, &src_path);
                    let src_secret: Result<Value, _> = {
                        let mut client = src_client.lock().unwrap();
                        client.secret_backend(&path.mount);
                        client.get_custom_secret(&src_path)
                    };
                    let dst_secret: Result<Value, _> = {
                        let mut client = dst_client.lock().unwrap();
                        client.secret_backend(mount_map[path.mount.as_str()]);
                        client.get_custom_secret(&dst_path)
                    };
                    if let Err(error) = src_secret {
                        warn!("Failed to get secret {}: {}", &src_path, error);
                        continue;
                    }
                    let src_secret = src_secret.unwrap();
                    if let Ok(dst_secret) = dst_secret {
                        if dst_secret == src_secret {
                            continue;
                        }
                    }
                    info!("Creating/updating secret {}", &dst_path);
                    if !dry_run {
                        let result = {
                            let client = dst_client.lock().unwrap();
                            client.set_custom_secret(&dst_path, &src_secret)
                        };
                        if let Err(error) = result {
                            warn!("Failed to set secret {}: {}", &dst_path, error);
                        } else {
                            stats.updated += 1;
                        }
                    }
                },
                SecretOp::Delete(path) => {
                    let secret = secret_src_to_dst_path(&src_prefix, &dst_prefix, &path.path);
                    info!("Deleting secret {}", &secret);
                    if !dry_run {
                        let mut client = dst_client.lock().unwrap();
                        client.secret_backend(mount_map[path.mount.as_str()]);
                        let _ = client.delete_secret(&path.path);
                    } else {
                        stats.deleted += 1;
                    }
                },
                SecretOp::FullSyncFinished => {
                    info!("Secrets created/updated: {}, deleted: {}", &stats.updated, &stats.deleted);
                    stats.reset();
                    if run_once {
                        break;
                    }
                },
            }
        }
    }
}


// Convert AuditLog to SecretOp
fn audit_log_op(mounts: &Vec<String>, prefix: &str, version: &EngineVersion, log: &audit::AuditLog) -> Option<SecretOp> {
    if log.log_type != "response" {
        return None;
    }
    if log.request.mount_type.is_none() {
        return None;
    }
    if log.request.mount_type != Some("kv".to_string()) {
        return None;
    }

    let operation = log.request.operation.clone();
    if operation != "create" && operation != "update" && operation != "delete" {
        return None;
    }

    let path = match version {
        EngineVersion::V1 => secret_path_v1(&log.request.path),
        EngineVersion::V2 => secret_path_v2(&log.request.path),
    };
    if let Some(path) = path {
        if !mounts.contains(&path.0) {
            return None;
        }
        if !path.1.starts_with(prefix) {
            return None;
        }
        if operation == "create" {
            return Some(SecretOp::Create(SecretPath {mount: path.0, path: path.1 }));
        } else if operation == "update" {
            return Some(SecretOp::Update(SecretPath {mount: path.0, path: path.1 }));
        } else if operation == "delete" {
            return Some(SecretOp::Delete(SecretPath {mount: path.0, path: path.1 }));
        }
    }
    None
}

// Convert Vault path to a secret path for KV v1
// Example: "secret/path/to/secret" -> "secret", "path/to/secret"
fn secret_path_v1(path: &str) -> Option<(String, String)> {
    let parts: Vec<&str> = path.split("/").collect();
    if parts.len() < 2 {
        return None
    }
    Some((parts[0].to_string(), parts[1..].join("/")))
}

// Convert Vault path to a secret path for KV v2
// Example: "secret/data/path/to/secret" -> "secret", "path/to/secret"
fn secret_path_v2(path: &str) -> Option<(String, String)> {
    let parts: Vec<&str> = path.split("/").collect();
    if parts.len() < 3 {
        return None
    }
    // `vault kv metadata delete secret/path` has `metadata` instead of `data`,
    // we do not support this yet
    if parts[1] == "data" {
        Some((parts[0].to_string(), parts[2..].join("/")))
    } else {
        None
    }
}

fn normalize_prefix(prefix: &str) -> String {
    if prefix.len() == 0 {
        return "".to_string();
    }
    if prefix.ends_with("/") {
        prefix.to_string()
    } else {
        format!("{}/", prefix)
    }
}

// Convert source secret path to destination secret path. Prefixes must be normalized!
// Example: "src/secret1" -> "dst/secret2"
fn secret_src_to_dst_path(src_prefix: &str, dst_prefix: &str, path: &str) -> String {
    let mut path = path.to_string();
    if src_prefix.len() > 0 {
        path = path.trim_start_matches(src_prefix).to_string();
    }
    format!("{}{}", dst_prefix, &path)
}

#[cfg(test)]
mod tests {
    use crate::sync::{normalize_prefix, secret_path_v1, secret_path_v2, secret_src_to_dst_path};

    #[test]
    fn test_secret_path_v1_matches() {
        let path = "secret/path/to/secret";
        let path = secret_path_v1(&path).unwrap();
        assert_eq!(path.0, "secret");
        assert_eq!(path.1, "path/to/secret");
    }

    #[test]
    fn test_custom_secret_path_v1_matches() {
        let path = "custom/path/to/secret";
        let path = secret_path_v1(&path).unwrap();
        assert_eq!(path.0, "custom");
        assert_eq!(path.1, "path/to/secret");
    }

    #[test]
    fn test_secret_path_v1_not_matches() {
        let path = "secret";
        let path = secret_path_v1(&path);
        assert_eq!(path.is_none(), true);
    }

    #[test]
    fn test_secret_path_v2_matches() {
        let path = "secret/data/path/to/secret";
        let path = secret_path_v2(&path).unwrap();
        assert_eq!(path.0, "secret");
        assert_eq!(path.1, "path/to/secret");
    }

    #[test]
    fn test_custom_secret_path_v2_matches() {
        let path = "custom/data/path/to/secret";
        let path = secret_path_v2(&path).unwrap();
        assert_eq!(path.0, "custom");
        assert_eq!(path.1, "path/to/secret");
    }

    #[test]
    fn test_secret_path_v2_not_matches() {
        let path = "secret/metadata/path/to/secret";
        let path = secret_path_v2(&path);
        assert_eq!(path.is_none(), true);
    }

    #[test]
    fn test_normalize_prefix() {
        assert_eq!(normalize_prefix(""), "");
        assert_eq!(normalize_prefix("src"), "src/");
        assert_eq!(normalize_prefix("src/"), "src/");
    }

    #[test]
    fn test_secret_src_to_dst_path() {
        assert_eq!(secret_src_to_dst_path("src/", "dst/", "src/secret"), "dst/secret");
        assert_eq!(secret_src_to_dst_path("", "dst/", "src/secret"), "dst/src/secret");
        assert_eq!(secret_src_to_dst_path("", "", "src/secret"), "src/secret");
    }

}
07070100000023000081A400000000000000000000000168156581000018B9000000000000000000000000000000000000001F00000000vault-sync-0.11.0/src/vault.rsuse std::{thread, time};
use std::sync::{Arc, Mutex};
use std::time::Duration;

use hashicorp_vault::client as vault;
use hashicorp_vault::client::{SecretsEngine, TokenData, VaultDuration};
use hashicorp_vault::client::error::Result as VaultResult;
use log::{info, warn};

use crate::config::{EngineVersion, VaultAuthMethod, VaultHost};

pub type VaultClient = hashicorp_vault::client::VaultClient<TokenData>;

pub fn vault_client(host: &VaultHost, version: &EngineVersion, namespace: Option<String>) -> VaultResult<vault::VaultClient<TokenData>> {
    let mut result = match host.auth.as_ref().unwrap() {
        VaultAuthMethod::TokenAuth { token } => {
            VaultClient::new(&host.url, token, namespace)
        },
        VaultAuthMethod::AppRoleAuth { role_id, secret_id} => {
            let client = vault::VaultClient::new_app_role(
                &host.url, role_id, Some(secret_id), namespace.clone())?;
            VaultClient::new(&host.url, client.token, namespace)
        }
    };

    if let Ok(client) = &mut result {
        client.secrets_engine(
            match version {
                EngineVersion::V1 => SecretsEngine::KVV1,
                EngineVersion::V2 => SecretsEngine::KVV2,
            }
        );
    }

    result
}

// Worker to renew a Vault token lease, or to request a new token (for Vault AppRole auth method)
pub fn token_worker(host: &VaultHost, version: &EngineVersion, client: Arc<Mutex<VaultClient>>, namespace: Option<String>) {
    let mut token_age = time::Instant::now();
    loop {
        let info = {
            let client = client.lock().unwrap();
            TokenInfo::from_client(&client)
        };
        info!("Token: {:?}", &info);

        // Override token TTL and max TTL with optional values from config
        let mut plan = info.clone();
        if let Some(token_ttl) = &host.token_ttl {
            match &info.ttl {
                Some(ttl) => {
                    if *token_ttl > 0 && *token_ttl < ttl.as_secs() {
                        plan.ttl = Some(Duration::from_secs(*token_ttl));
                    }
                },
                None => {
                    plan.ttl = Some(Duration::from_secs(*token_ttl))
                }
            }
        }
        if let Some(token_max_ttl) = &host.token_max_ttl {
            match &info.max_ttl {
                Some(max_ttl) => {
                    if *token_max_ttl > 0 && *token_max_ttl < max_ttl.as_secs() {
                        plan.max_ttl = Some(Duration::from_secs(*token_max_ttl));
                    }
                },
                None => {
                    plan.max_ttl = Some(Duration::from_secs(*token_max_ttl))
                }
            }
        }
        info!("Plan: {:?}", &plan);

        if !plan.renewable {
            return;
        } else {
            if let Some(VaultAuthMethod::AppRoleAuth { role_id: _, secret_id: _ }) = &host.auth {
                if plan.max_ttl.is_none() {
                    warn!("Auth method is AppRole, but max_ttl is not set, using 32 days instead");
                    plan.max_ttl = Some(time::Duration::from_secs(32 * 24 * 60 * 60));
                }
            }
            if let Some(VaultAuthMethod::TokenAuth { token: _ }) = &host.auth {
                if plan.max_ttl.is_some() {
                    info!("Auth method is Token, but max_ttl is set, ignoring");
                    plan.max_ttl = None;
                }
            }
        }

        let duration = {
            if plan.ttl.is_none() {
                plan.max_ttl.unwrap()
            } else if plan.max_ttl.is_none() {
                plan.ttl.unwrap()
            } else {
                plan.ttl.unwrap().min(plan.max_ttl.unwrap())
            }
        };
        let duration = time::Duration::from_secs(duration.as_secs() / 2);

        thread::sleep(duration);

        if let Some(max_ttl) = plan.max_ttl {
            let age = token_age.elapsed().as_secs();
            let max_ttl = max_ttl.as_secs();
            if age > max_ttl / 2 {
                if let Some(VaultAuthMethod::AppRoleAuth { role_id: _, secret_id: _ }) = &host.auth {
                    info!("Requesting a new token");
                    match vault_client(&host, &version, namespace.clone()) {
                        Ok(new_client) => {
                            let mut client = client.lock().unwrap();
                            client.token = new_client.token;
                            client.data = new_client.data;
                            token_age = time::Instant::now();
                            continue;
                        },
                        Err(error) => {
                            warn!("Failed to request a new token: {}", error);
                        }
                    }
                }
            }
        }

        if let Some(_) = plan.ttl {
            info!("Renewing token");
            let result = {
                let mut client = client.lock().unwrap();
                client.renew()
            };
            if let Err(error) = result {
                warn!("Failed to renew token: {}", error);
            }
        }
    }
}

#[derive(Debug, Clone)]
struct TokenInfo {
    renewable: bool,
    ttl: Option<Duration>,
    max_ttl: Option<Duration>,
}

impl TokenInfo {
    fn new() -> TokenInfo {
        // Defaults are for the root token, which is not renewable and has no TTL and max TTL
        TokenInfo {
            renewable: false,
            ttl: None,
            max_ttl: None,
        }
    }

    fn from_client(client: &VaultClient) -> TokenInfo {
        let mut info = Self::new();
        if let Some(data) = &client.data {
            if let Some(data) = &data.data {
                let zero_duration = VaultDuration::seconds(0);
                info.renewable = data.renewable.unwrap_or(false);
                let ttl_duration= &data.ttl;
                if ttl_duration.0.as_secs() > 0 {
                    info.ttl = Some(ttl_duration.0);
                }

                let max_ttl_duration = data.explicit_max_ttl.as_ref().unwrap_or(&zero_duration);
                if max_ttl_duration.0.as_secs() > 0 {
                    info.max_ttl = Some(max_ttl_duration.0);
                }
            }
        }
        info
    }
}
07070100000024000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000001B00000000vault-sync-0.11.0/vault-rs07070100000025000081A40000000000000000000000016815658100000012000000000000000000000000000000000000002600000000vault-sync-0.11.0/vault-rs/.gitignoretarget
Cargo.lock
07070100000026000081A400000000000000000000000168156581000002E6000000000000000000000000000000000000002600000000vault-sync-0.11.0/vault-rs/Cargo.toml[package]
name = "hashicorp_vault"
version = "2.1.1"
edition = "2018"
authors = [
  "Chris MacNaughton <chmacnaughton@gmail.com>",
  "Christopher Brickley <brickley@gmail.com>"
]
description = "HashiCorp Vault API client for Rust"
license = "MIT"
repository = "https://github.com/chrismacnaughton/vault-rs"

[features]
default = ["native-tls"]
native-tls = ["reqwest/native-tls"]
rustls-tls = ["reqwest/rustls-tls"]

[dependencies]
base64 = "~0.13"
chrono = "~0.4"
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0"
reqwest = { version = "~0.11", default-features = false, features = ["blocking"] }
log = "^0.4"
quick-error = "~2.0"
url = "^2.3"

[dependencies.clippy]
optional = true
version = "^0.0"
07070100000027000081A400000000000000000000000168156581000000E6000000000000000000000000000000000000002500000000vault-sync-0.11.0/vault-rs/README.mdThis directory contains a fork of https://github.com/ChrisMacNaughton/vault-rs.

The original project support only KV v1, and the fork contains fast and dirty changes to support KV v1 and v2.
PR to the upstream project to follow.
07070100000028000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000001F00000000vault-sync-0.11.0/vault-rs/src07070100000029000041ED0000000000000000000000026815658100000000000000000000000000000000000000000000002600000000vault-sync-0.11.0/vault-rs/src/client0707010000002A000081A4000000000000000000000001681565810000056D000000000000000000000000000000000000002F00000000vault-sync-0.11.0/vault-rs/src/client/error.rs/// `Result` type-alias
pub type Result<T> = ::std::result::Result<T, Error>;

quick_error! {
    /// Error enum for vault-rs
    #[derive(Debug)]
    pub enum Error {
        /// `reqwest::Error` errors
        Reqwest(err: ::reqwest::Error) {
            from()
            display("reqwest error: {}", err)
            source(err)
        }
        /// `serde_json::Error`
        SerdeJson(err: ::serde_json::Error) {
            from()
            display("serde_json Error: {}", err)
            source(err)
        }
        /// Vault errors
        Vault(err: String) {
            display("vault error: {}", err)
        }
        /// Response from Vault errors
        /// This is for when the response is not successful.
        VaultResponse(err: String, response: reqwest::blocking::Response) {
            display("Error in vault response: {}", err)
        }
        /// IO errors
        Io(err: ::std::io::Error) {
            from()
            display("io error: {}", err)
            source(err)
        }
        /// `Url` parsing error
        Url(err: ::url::ParseError) {
            from()
            display("url parse error: {}", err)
            source(err)
        }
        /// `Base64` decode error
        Base64(err: ::base64::DecodeError) {
            from()
            display("base64 decode error: {}", err)
            source(err)
        }
    }
}
0707010000002B000081A4000000000000000000000001681565810000EBDF000000000000000000000000000000000000002D00000000vault-sync-0.11.0/vault-rs/src/client/mod.rsuse std::collections::HashMap;
use std::fmt;
use std::io::Read;
use std::num::NonZeroU64;
use std::result::Result as StdResult;
use std::str::FromStr;

use crate::client::error::{Error, Result};
use base64;
use reqwest::{
    self,
    blocking::{Client, Response},
    header::CONTENT_TYPE,
    Method,
};
use serde::de::{self, DeserializeOwned, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::TryInto;
use chrono::{DateTime, FixedOffset, Utc};
use serde_json;
use std::time::Duration;
use url::Url;

/// Errors
pub mod error;

/// Secrets engine.
///
/// See https://developer.hashicorp.com/vault/api-docs/secret/kv.
#[derive(Debug, PartialEq, Eq)]
pub enum SecretsEngine {
    /// KV secrets engine, version 1.
    /// https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v1
    KVV1,
    /// KV secrets engine, version 2.
    /// https://developer.hashicorp.com/vault/api-docs/secret/kv/kv-v2
    KVV2,
}

/// Lease duration.
///
/// Note: Value returned from vault api is assumed to be in seconds.
///
/// ```
/// use hashicorp_vault::client::VaultDuration;
///
/// assert_eq!(VaultDuration::days(1),
///            VaultDuration(std::time::Duration::from_secs(86400)));
/// ```
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct VaultDuration(pub Duration);

impl VaultDuration {
    /// Construct a duration from some number of seconds.
    pub fn seconds(s: u64) -> VaultDuration {
        VaultDuration(Duration::from_secs(s))
    }

    /// Construct a duration from some number of minutes.
    pub fn minutes(m: u64) -> VaultDuration {
        VaultDuration::seconds(m * 60)
    }

    /// Construct a duration from some number of hours.
    pub fn hours(h: u64) -> VaultDuration {
        VaultDuration::minutes(h * 60)
    }

    /// Construct a duration from some number of days.
    pub fn days(d: u64) -> VaultDuration {
        VaultDuration::hours(d * 24)
    }
}

impl Serialize for VaultDuration {
    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_u64(self.0.as_secs())
    }
}
struct VaultDurationVisitor;
impl<'de> Visitor<'de> for VaultDurationVisitor {
    type Value = VaultDuration;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a positive integer")
    }

    fn visit_u64<E>(self, value: u64) -> StdResult<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(VaultDuration(Duration::from_secs(value)))
    }
}
impl<'de> Deserialize<'de> for VaultDuration {
    fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_u64(VaultDurationVisitor)
    }
}

/// Number of uses to be used with tokens.
///
/// Note: Value returned from vault api can be 0 which means unlimited.
///
/// ```
/// use hashicorp_vault::client::VaultNumUses;
/// use std::num::NonZeroU64;
///
/// let num_uses: VaultNumUses = 10.into();
///
/// match num_uses {
///     VaultNumUses::Limited(uses) => assert_eq!(uses.get(), 10),
///     VaultNumUses::Unlimited => panic!("Uses shouldn't be unlimited!"),
/// }
/// ```
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum VaultNumUses {
    /// The number of uses is unlimited
    Unlimited,

    /// The number of uses is limited to the value
    /// specified that is guaranteed to be non zero.
    Limited(NonZeroU64),
}

impl Default for VaultNumUses {
    fn default() -> Self {
        VaultNumUses::Unlimited
    }
}

impl From<u64> for VaultNumUses {
    fn from(v: u64) -> Self {
        match NonZeroU64::new(v) {
            Some(non_zero) => VaultNumUses::Limited(non_zero),
            None => VaultNumUses::Unlimited,
        }
    }
}

impl Serialize for VaultNumUses {
    fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            VaultNumUses::Unlimited => serializer.serialize_u64(0),
            VaultNumUses::Limited(val) => serializer.serialize_u64(val.clone().into()),
        }
    }
}
struct VaultNumUsesVisitor;
impl<'de> Visitor<'de> for VaultNumUsesVisitor {
    type Value = VaultNumUses;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a positive integer")
    }

    fn visit_u64<E>(self, value: u64) -> StdResult<Self::Value, E>
    where
        E: de::Error,
    {
        Ok(value.into())
    }
}
impl<'de> Deserialize<'de> for VaultNumUses {
    fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_u64(VaultNumUsesVisitor)
    }
}

/// Used for vault responses that return seconds since unix epoch
/// See: https://github.com/hashicorp/vault/issues/1654
#[derive(Debug)]
pub struct VaultNaiveDateTime(pub DateTime<Utc>);
struct VaultNaiveDateTimeVisitor;
impl<'de> Visitor<'de> for VaultNaiveDateTimeVisitor {
    type Value = VaultNaiveDateTime;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a positive integer")
    }

    fn visit_u64<E>(self, value: u64) -> StdResult<Self::Value, E>
    where
        E: de::Error,
    {
        let date_time = DateTime::from_timestamp(value as i64, 0);

        match date_time {
            Some(dt) => Ok(VaultNaiveDateTime(dt)),
            None => Err(E::custom(format!(
                "Could not parse: `{}` as a unix timestamp",
                value,
            ))),
        }
    }
}
impl<'de> Deserialize<'de> for VaultNaiveDateTime {
    fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_u64(VaultNaiveDateTimeVisitor)
    }
}

/// Used for responses that return RFC 3339 timestamps
/// See: https://github.com/hashicorp/vault/issues/1654
#[derive(Debug)]
pub struct VaultDateTime(pub DateTime<FixedOffset>);
struct VaultDateTimeVisitor;
impl<'de> Visitor<'de> for VaultDateTimeVisitor {
    type Value = VaultDateTime;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a timestamp string")
    }

    fn visit_str<E>(self, value: &str) -> StdResult<Self::Value, E>
    where
        E: de::Error,
    {
        let date_time = DateTime::parse_from_rfc3339(value);
        match date_time {
            Ok(dt) => Ok(VaultDateTime(dt)),
            Err(e) => Err(E::custom(format!(
                "Could not parse: `{}` as an RFC 3339 timestamp. Error: \
                 `{:?}`",
                value, e
            ))),
        }
    }
}
impl<'de> Deserialize<'de> for VaultDateTime {
    fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(VaultDateTimeVisitor)
    }
}

/// Vault client used to make API requests to the vault
#[derive(Debug)]
pub struct VaultClient<T> {
    /// URL to vault instance
    pub host: Url,
    /// Token to access vault
    pub token: String,
    /// Namespace (optional)
    pub namespace: Option<String>,
    /// `reqwest::Client`
    client: Client,
    /// Data
    pub data: Option<VaultResponse<T>>,
    /// The secret backend name. Defaults to 'secret'
    secret_backend: String,
    /// Secrets engine
    secrets_engine: SecretsEngine,
}

/// Token data, used in `VaultResponse`
#[derive(Deserialize, Debug)]
pub struct TokenData {
    /// Accessor token
    pub accessor: Option<String>,
    /// Creation time
    pub creation_time: VaultNaiveDateTime,
    /// Creation time-to-live
    pub creation_ttl: Option<VaultDuration>,
    /// Display name
    pub display_name: String,
    /// Max time-to-live
    pub explicit_max_ttl: Option<VaultDuration>,
    /// Token id
    pub id: String,
    /// Last renewal time
    pub last_renewal_time: Option<VaultDuration>,
    /// Meta
    pub meta: Option<HashMap<String, String>>,
    /// Number of uses (0: unlimited)
    pub num_uses: VaultNumUses,
    /// true if token is an orphan
    pub orphan: bool,
    /// Path
    pub path: String,
    /// Policies for token
    pub policies: Vec<String>,
    /// True if renewable
    pub renewable: Option<bool>,
    /// Role
    pub role: Option<String>,
    /// Time-to-live
    pub ttl: VaultDuration,
}

/// Secret data, used in `VaultResponse`
///
/// This struct should onlly ever be necessary for advanced users who
/// are creating and parsing Vault responses manually.
#[derive(Deserialize, Serialize, Debug)]
pub struct SecretDataWrapper<D> {
    /// data is an opaque data type that holds the response from Vault.
    pub data: D,
}

/// Actual Secret data, used in `VaultResponse`
#[derive(Deserialize, Serialize, Debug)]
struct SecretData {
    value: String,
}

/// Transit decrypted data, used in `VaultResponse`
#[derive(Deserialize, Serialize, Debug)]
struct TransitDecryptedData {
    plaintext: String,
}

/// Transit encrypted data, used in `VaultResponse`
#[derive(Deserialize, Serialize, Debug)]
struct TransitEncryptedData {
    ciphertext: String,
}

/// Vault auth
#[derive(Deserialize, Debug)]
pub struct Auth {
    /// Client token id
    pub client_token: String,
    /// Accessor
    pub accessor: Option<String>,
    /// Policies
    pub policies: Vec<String>,
    /// Metadata
    pub metadata: Option<HashMap<String, String>>,
    /// Lease duration
    pub lease_duration: Option<VaultDuration>,
    /// True if renewable
    pub renewable: bool,
}

/// Vault response. Different vault responses have different `data` types, so `D` is used to
/// represent this.
#[derive(Deserialize, Debug)]
pub struct VaultResponse<D> {
    /// Request id
    pub request_id: String,
    /// Lease id
    pub lease_id: Option<String>,
    /// True if renewable
    pub renewable: Option<bool>,
    /// Lease duration (in seconds)
    pub lease_duration: Option<VaultDuration>,
    /// Data
    pub data: Option<D>,
    /// Warnings
    pub warnings: Option<Vec<String>>,
    /// Auth
    pub auth: Option<Auth>,
    /// Wrap info, containing token to perform unwrapping
    pub wrap_info: Option<WrapInfo>,
}

impl<D> From<VaultResponse<SecretDataWrapper<D>>> for VaultResponse<D> {
    fn from(v: VaultResponse<SecretDataWrapper<D>>) -> Self {
        Self {
            request_id: v.request_id,
            lease_id: v.lease_id,
            renewable: v.renewable,
            lease_duration: v.lease_duration,
            data: v.data.map(|value| value.data),
            warnings: v.warnings,
            auth: v.auth,
            wrap_info: v.wrap_info,
        }
    }
}

/// Information provided to retrieve a wrapped response
#[derive(Deserialize, Debug)]
pub struct WrapInfo {
    /// Time-to-live
    pub ttl: VaultDuration,
    /// Token
    pub token: String,
    /// Creation time, note this returned in RFC 3339 format
    pub creation_time: VaultDateTime,
    /// Wrapped accessor
    pub wrapped_accessor: Option<String>,
}

/// Wrapped response is serialized json
#[derive(Deserialize, Serialize, Debug)]
pub struct WrapData {
    /// Serialized json string of type `VaultResponse<HashMap<String, String>>`
    response: String,
}

/// Token Types
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum TokenType {
    /// Batch tokens are encrypted blobs that carry enough information
    /// for them to be used for Vault actions, but they require no
    /// storage on disk to track them.
    Batch,
    /// Service tokens are what users will generally think of as
    /// "normal" Vault tokens.
    Service,
    /// Will use the mount's tuned default.
    Default,
    /// For a token store this will default to batch, unless the client requests
    /// a different type at generation time.
    DefaultBatch,
    /// For a token store this will default to service, unless the client requests
    /// a different type at generation time.
    DefaultService,
}

/// `AppRole` properties
#[derive(Deserialize, Debug)]
pub struct AppRoleProperties {
    /// Require `secret_id` to be presented when logging in using this `AppRole`. Defaults to 'true'.
    pub bind_secret_id: bool,
    /// The secret IDs generated using this role will be cluster local.
    pub local_secret_ids: bool,
    /// List of CIDR blocks; if set, specifies blocks of IP addresses which can
    /// perform the login operation.
    pub secret_id_bound_cidrs: Option<Vec<String>>,
    /// Number of times any particular `SecretID` can be used to fetch a token from this `AppRole`,
    /// after which the `SecretID` will expire.
    pub secret_id_num_uses: VaultNumUses,
    /// Duration in either an integer number of seconds (3600) or an integer time unit (60m) after which any SecretID expires.
    pub secret_id_ttl: VaultDuration,
    /// List of CIDR blocks; if set, specifies blocks of IP addresses which can authenticate successfully,
    /// and ties the resulting token to these blocks as well.
    pub token_bound_cidrs: Option<Vec<String>>,
    /// If set, will encode an explicit max TTL onto the token. This is a hard cap even if token_ttl and
    /// token_max_ttl would otherwise allow a renewal.
    pub token_explicit_max_ttl: Option<VaultDuration>,
    /// If set, the default policy will not be set on generated tokens; otherwise it will be added to
    /// the policies set in token_policies.
    pub token_no_default_policy: Option<bool>,
    /// Duration after which the issued token can no longer be renewed.
    pub token_max_ttl: VaultDuration,
    /// The maximum number of times a generated token may be used (within its lifetime).
    pub token_num_uses: VaultNumUses,
    /// The incremental lifetime for generated tokens.
    /// If set, the token generated using this `AppRole` is a periodic token; so long as it is
    /// renewed it never expires, but the TTL set on the token at each renewal is fixed to the value
    /// specified here. If this value is modified, the token will pick up the new value at its next
    /// renewal.
    pub token_period: Option<VaultDuration>,
    /// List of policies to encode onto generated tokens.
    pub token_policies: Option<Vec<String>>,
    /// The incremental lifetime for generated tokens.
    pub token_ttl: VaultDuration,
    /// The type of token that should be generated. Can be service, batch, or default to use the mount's
    /// tuned default (which unless changed will be service tokens). For token store roles, there are two
    /// additional possibilities: default-service and default-batch which specify the type to return unless
    /// the client requests a different type at generation time.
    pub token_type: TokenType,
}

/// Payload to send to vault when authenticating via `AppId`
#[derive(Deserialize, Serialize, Debug)]
struct AppIdPayload {
    app_id: String,
    user_id: String,
}

/// Payload to send to vault when authenticating via `AppRole`
#[derive(Deserialize, Serialize, Debug)]
struct AppRolePayload {
    role_id: String,
    secret_id: Option<String>,
}

/// Postgresql secret backend
#[derive(Deserialize, Serialize, Debug)]
pub struct PostgresqlLogin {
    /// Password
    pub password: String,
    /// Username
    pub username: String,
}

/// Response sent by vault when listing policies.  We hide this from the
/// caller.
#[derive(Deserialize, Serialize, Debug)]
struct PoliciesResponse {
    policies: Vec<String>,
}

/// Response sent by vault when issuing a `LIST` request.
#[derive(Deserialize, Serialize, Debug)]
pub struct ListResponse {
    /// keys will include the items listed
    pub keys: Vec<String>,
}

/// Options that we use when renewing tokens.
#[derive(Deserialize, Serialize, Debug)]
struct RenewTokenOptions {
    /// Token to renew. This can be part of the URL or the body.
    token: String,
    /// The amount of time for which to renew the lease.  May be ignored or
    /// overriden by vault.
    increment: Option<u64>,
}

/// Options that we use when renewing leases.
#[derive(Deserialize, Serialize, Debug)]
struct RenewLeaseOptions {
    lease_id: String,
    /// The amount of time for which to renew the lease.  May be ignored or
    /// overriden by vault.
    increment: Option<u64>,
}

/// Options for creating a token.  This is intended to be used as a
/// "builder"-style interface, where you create a new `TokenOptions`
/// object, call a bunch of chained methods on it, and then pass the result
/// to `Client::create_token`.
///
/// ```
/// use hashicorp_vault::client::{TokenOptions, VaultDuration};
///
/// let _ = TokenOptions::default()
///   .id("test12345")
///   .policies(vec!("root"))
///   .default_policy(false)
///   .orphan(true)
///   .renewable(false)
///   .display_name("jdoe-temp")
///   .number_of_uses(10)
///   .ttl(VaultDuration::hours(3))
///   .explicit_max_ttl(VaultDuration::hours(13));
/// ```
///
/// If an option is not specified, it will be set according to [Vault's
/// standard defaults for newly-created tokens][token].
///
/// [token]: https://www.vaultproject.io/docs/auth/token.html
#[derive(Default, Serialize, Debug)]
pub struct TokenOptions {
    id: Option<String>,
    policies: Option<Vec<String>>,
    // TODO: `meta`
    no_parent: Option<bool>,
    no_default_policy: Option<bool>,
    renewable: Option<bool>,
    ttl: Option<String>,
    explicit_max_ttl: Option<String>,
    display_name: Option<String>,
    num_uses: VaultNumUses,
}

impl TokenOptions {
    /// Set the `id` of the created token to the specified value.  **This
    /// may make it easy for attackers to guess your token.** Typically,
    /// this is used for testing and similar purposes.
    pub fn id<S: Into<String>>(mut self, id: S) -> Self {
        self.id = Some(id.into());
        self
    }

    /// Supply a list of policies that will be used to grant permissions to
    /// the created token.  Unless you also call `default_policy(false)`, the
    /// policy `default` will be added to this list in modern versions of
    /// vault.
    pub fn policies<I>(mut self, policies: I) -> Self
    where
        I: IntoIterator,
        I::Item: Into<String>,
    {
        self.policies = Some(policies.into_iter().map(|p| p.into()).collect());
        self
    }

    /// Should we grant access to the `default` policy?  Defaults to true.
    pub fn default_policy(mut self, enable: bool) -> Self {
        self.no_default_policy = Some(!enable);
        self
    }

    /// Should this token be an "orphan", allowing it to survive even when
    /// the token that created it expires or is revoked?
    pub fn orphan(mut self, orphan: bool) -> Self {
        self.no_parent = Some(!orphan);
        self
    }

    /// Should the token be renewable?
    pub fn renewable(mut self, renewable: bool) -> Self {
        self.renewable = Some(renewable);
        self
    }

    /// For various logging purposes, what should this token be called?
    pub fn display_name<S>(mut self, name: S) -> Self
    where
        S: Into<String>,
    {
        self.display_name = Some(name.into());
        self
    }

    /// How many times can this token be used before it stops working?
    pub fn number_of_uses<D: Into<VaultNumUses>>(mut self, uses: D) -> Self {
        self.num_uses = uses.into();
        self
    }

    /// How long should this token remain valid for?
    pub fn ttl<D: Into<VaultDuration>>(mut self, ttl: D) -> Self {
        self.ttl = Some(format!("{}s", ttl.into().0.as_secs()));
        self
    }

    /// How long should this token remain valid for, even if it is renewed
    /// repeatedly?
    pub fn explicit_max_ttl<D: Into<VaultDuration>>(mut self, ttl: D) -> Self {
        self.explicit_max_ttl = Some(format!("{}s", ttl.into().0.as_secs()));
        self
    }
}

/// http verbs
#[derive(Debug)]
pub enum HttpVerb {
    /// GET
    GET,
    /// POST
    POST,
    /// PUT
    PUT,
    /// DELETE
    DELETE,
    /// LIST
    LIST,
}

#[derive(Debug, Serialize)]
struct SecretContainer<T: Serialize> {
    data: T,
}

#[derive(Debug, Deserialize, Serialize)]
struct DefaultSecretType<T: AsRef<str>> {
    value: T,
}

/// endpoint response variants
#[derive(Debug)]
pub enum EndpointResponse<D> {
    /// Vault response
    VaultResponse(VaultResponse<D>),
    /// Empty, but still successful response
    Empty,
}

impl VaultClient<TokenData> {
    /// Construct a `VaultClient` from an existing vault token
    pub fn new<U, T: Into<String>>(host: U, token: T, namespace: Option<String>) -> Result<VaultClient<TokenData>>
    where
        U: TryInto<Url, Err = Error>,
    {
        let host = host.try_into()?;
        let client = Client::new();
        let token = token.into();
        let mut request = client
            .get(host.join("/v1/auth/token/lookup-self")?)
            .header("X-Vault-Token", token.clone());

        if let Some(ns) = &namespace {
            if !ns.is_empty() {
                request = request.header("X-Vault-Namespace", ns.clone());
            }
        }

        let res = handle_reqwest_response(request.send())?;
        let decoded: VaultResponse<TokenData> = parse_vault_response(res)?;
        Ok(VaultClient {
            host,
            token,
            namespace,
            client,
            data: Some(decoded),
            secret_backend: "secret".into(),
            secrets_engine: SecretsEngine::KVV2,
        })
    }
    /// Construct a `VaultClient` from an existing vault token and reqwest::Client
    pub fn new_from_reqwest<U, T: Into<String>>(
        host: U,
        token: T,
        cli: Client,
        namespace: Option<String>,
    ) -> Result<VaultClient<TokenData>>
    where
        U: TryInto<Url, Err = Error>,
    {
        let host = host.try_into()?;
        let client = cli;
        let token = token.into();
        let mut request = client
            .get(host.join("/v1/auth/token/lookup-self")?)
            .header("X-Vault-Token", token.clone());

        if let Some(ns) = &namespace {
            if !ns.is_empty() {
                request = request.header("X-Vault-Namespace", ns.clone());
            }
        }

        let res = handle_reqwest_response(request.send())?;
        let decoded: VaultResponse<TokenData> = parse_vault_response(res)?;
        Ok(VaultClient {
            host,
            token,
            client,
            namespace,
            data: Some(decoded),
            secret_backend: "secret".into(),
            secrets_engine: SecretsEngine::KVV2,
        })
    }
}

impl VaultClient<()> {
    /// Construct a `VaultClient` via the `App ID`
    /// [auth backend](https://www.vaultproject.io/docs/auth/app-id.html)
    ///
    /// NOTE: This backend is now deprecated by vault.
    #[deprecated(since = "0.6.1")]
    pub fn new_app_id<U, S1: Into<String>, S2: Into<String>>(
        host: U,
        app_id: S1,
        user_id: S2,
        namespace: Option<String>,
    ) -> Result<VaultClient<()>>
    where
        U: TryInto<Url, Err = Error>,
    {
        let host = host.try_into()?;
        let client = Client::new();
        let payload = serde_json::to_string(&AppIdPayload {
            app_id: app_id.into(),
            user_id: user_id.into(),
        })?;
        let mut request = client
            .post(host.join("/v1/auth/app-id/login")?)
            .body(payload);

        if let Some(ns) = &namespace {
            if !ns.is_empty() {
                request = request.header("X-Vault-Namespace", ns.clone());
            }
        }

        let res = handle_reqwest_response(request.send())?;
        let decoded: VaultResponse<()> = parse_vault_response(res)?;
        let token = match decoded.auth {
            Some(ref auth) => auth.client_token.clone(),
            None => {
                return Err(Error::Vault(format!(
                    "No client token found in response: `{:?}`",
                    &decoded.auth
                )))
            }
        };
        Ok(VaultClient {
            host,
            token,
            namespace,
            client,
            data: Some(decoded),
            secret_backend: "secret".into(),
            secrets_engine: SecretsEngine::KVV2,
        })
    }

    /// Construct a `VaultClient` via the `AppRole`
    /// [auth backend](https://www.vaultproject.io/docs/auth/approle.html)
    pub fn new_app_role<U, R, S>(
        host: U,
        role_id: R,
        secret_id: Option<S>,
        namespace: Option<String>,
    ) -> Result<VaultClient<()>>
    where
        U: TryInto<Url, Err = Error>,
        R: Into<String>,
        S: Into<String>,
    {
        let host = host.try_into()?;
        let client = Client::new();
        let secret_id = secret_id.map(|s| s.into());
        let payload = serde_json::to_string(&AppRolePayload {
            role_id: role_id.into(),
            secret_id,
        })?;
        let mut request = client
            .post(host.join("/v1/auth/approle/login")?)
            .body(payload);

        if let Some(ns) = &namespace {
            if !ns.is_empty() {
                request = request.header("X-Vault-Namespace", ns.clone());
            }
        }

        let res = handle_reqwest_response(request.send())?;
        let decoded: VaultResponse<()> = parse_vault_response(res)?;
        let token = match decoded.auth {
            Some(ref auth) => auth.client_token.clone(),
            None => {
                return Err(Error::Vault(format!(
                    "No client token found in response: `{:?}`",
                    &decoded.auth
                )))
            }
        };
        Ok(VaultClient {
            host,
            token,
            namespace,
            client,
            data: Some(decoded),
            secret_backend: "secret".into(),
            secrets_engine: SecretsEngine::KVV2,
        })
    }

    /// Construct a `VaultClient` where no lookup is done through vault since it is assumed that the
    /// provided token is a single-use token.
    ///
    /// A common use case for this method is when a `wrapping_token` has been received and you want
    /// to query the `sys/wrapping/unwrap` endpoint.
    pub fn new_no_lookup<U, S: Into<String>>(host: U, token: S, namespace: Option<String>) -> Result<VaultClient<()>>
    where
        U: TryInto<Url, Err = Error>,
    {
        let client = Client::new();
        let host = host.try_into()?;
        Ok(VaultClient {
            host,
            token: token.into(),
            namespace,
            client,
            data: None,
            secret_backend: "secret".into(),
            secrets_engine: SecretsEngine::KVV2,
        })
    }
}

impl<T> VaultClient<T>
where
    T: DeserializeOwned,
{
    /// Set the backend name to be used by this VaultClient
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let mut client = Client::new(host, token, namespace).unwrap();
    /// client.secret_backend("my_secrets");
    /// ```
    pub fn secret_backend<S1: Into<String>>(&mut self, backend_name: S1) {
        self.secret_backend = backend_name.into();
    }

    /// Set the secrets engine to be used by this VaultClient
    pub fn secrets_engine(&mut self, secrets_engine: SecretsEngine) {
        self.secrets_engine = secrets_engine
    }

    /// Renew lease for `VaultClient`'s token and updates the
    /// `self.data.auth` based upon the response.  Corresponds to
    /// [`/auth/token/renew-self`][token].
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let mut client = Client::new(host, token, namespace).unwrap();
    ///
    /// client.renew().unwrap();
    /// ```
    ///
    /// [token]: https://www.vaultproject.io/docs/auth/token.html
    pub fn renew(&mut self) -> Result<()> {
        let res = self.post::<_, String>("/v1/auth/token/renew-self", None, None)?;
        let vault_res: VaultResponse<T> = parse_vault_response(res)?;
        if let Some(ref mut data) = self.data {
            data.auth = vault_res.auth;
        }
        Ok(())
    }

    /// Renew the lease for the specified token.  Requires `root`
    /// privileges.  Corresponds to [`/auth/token/renew[/token]`][token].
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    ///
    /// let token_to_renew = "test12345";
    /// client.renew_token(token_to_renew, None).unwrap();
    /// ```
    ///
    /// [token]: https://www.vaultproject.io/docs/auth/token.html
    pub fn renew_token<S: Into<String>>(&self, token: S, increment: Option<u64>) -> Result<Auth> {
        let body = serde_json::to_string(&RenewTokenOptions {
            token: token.into(),
            increment,
        })?;
        let res = self.post::<_, String>("/v1/auth/token/renew", Some(&body), None)?;
        let vault_res: VaultResponse<()> = parse_vault_response(res)?;
        vault_res
            .auth
            .ok_or_else(|| Error::Vault("No auth data returned while renewing token".to_owned()))
    }

    /// Revoke `VaultClient`'s token. This token can no longer be used.
    /// Corresponds to [`/auth/token/revoke-self`][token].
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::{client, Client};
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    ///
    /// // Create a temporary token, and use it to create a new client.
    /// let opts = client::TokenOptions::default()
    ///   .ttl(client::VaultDuration::minutes(5));
    /// let res = client.create_token(&opts).unwrap();
    /// let namespace: Option<String> = None;
    /// let mut new_client = Client::new(host, res.client_token, namespace).unwrap();
    ///
    /// // Issue and use a bunch of temporary dynamic credentials.
    ///
    /// // Revoke all our dynamic credentials with a single command.
    /// new_client.revoke().unwrap();
    /// ```
    ///
    /// Note that we consume our `self` parameter, so you cannot use the
    /// client after revoking it.
    ///
    /// [token]: https://www.vaultproject.io/docs/auth/token.html
    pub fn revoke(self) -> Result<()> {
        let _ = self.post::<_, String>("/v1/auth/token/revoke-self", None, None)?;
        Ok(())
    }

    /// Renew a specific lease that your token controls.  Corresponds to
    /// [`/v1/sys/lease`][renew].
    ///
    /// ```no_run
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    /// use serde::Deserialize;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    ///
    /// #[derive(Deserialize)]
    /// struct PacketKey {
    ///   api_key_token: String,
    /// }
    ///
    /// let res = client.get_secret_engine_creds::<PacketKey>("packet", "1h-read-only-user").unwrap();
    ///
    /// client.renew_lease(res.lease_id.unwrap(), None).unwrap();
    /// ```
    ///
    /// [renew]: https://www.vaultproject.io/docs/http/sys-renew.html
    pub fn renew_lease<S: Into<String>>(
        &self,
        lease_id: S,
        increment: Option<u64>,
    ) -> Result<VaultResponse<()>> {
        let body = serde_json::to_string(&RenewLeaseOptions {
            lease_id: lease_id.into(),
            increment,
        })?;
        let res = self.put::<_, String>("/v1/sys/leases/renew", Some(&body), None)?;
        let vault_res: VaultResponse<()> = parse_vault_response(res)?;
        Ok(vault_res)
    }

    /// Lookup token information for this client's token.  Corresponds to
    /// [`/auth/token/lookup-self`][token].
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    ///
    /// let res = client.lookup().unwrap();
    /// assert!(res.data.unwrap().policies.len() >= 0);
    /// ```
    ///
    /// [token]: https://www.vaultproject.io/docs/auth/token.html
    pub fn lookup(&self) -> Result<VaultResponse<TokenData>> {
        let res = self.get::<_, String>("/v1/auth/token/lookup-self", None)?;
        let vault_res: VaultResponse<TokenData> = parse_vault_response(res)?;
        Ok(vault_res)
    }

    /// Create a new vault token using the specified options.  Corresponds to
    /// [`/auth/token/create`][token].
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::{client, Client};
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    ///
    /// let opts = client::TokenOptions::default()
    ///   .display_name("test_token")
    ///   .policies(vec!("root"))
    ///   .default_policy(false)
    ///   .orphan(true)
    ///   .renewable(false)
    ///   .display_name("jdoe-temp")
    ///   .number_of_uses(10)
    ///   .ttl(client::VaultDuration::minutes(1))
    ///   .explicit_max_ttl(client::VaultDuration::minutes(3));
    /// let res = client.create_token(&opts).unwrap();
    ///  let namespace: Option<String> = None;
    /// # let new_client = Client::new(host, res.client_token, namespace).unwrap();
    /// # new_client.revoke().unwrap();
    /// ```
    ///
    /// [token]: https://www.vaultproject.io/docs/auth/token.html
    pub fn create_token(&self, opts: &TokenOptions) -> Result<Auth> {
        let body = serde_json::to_string(opts)?;
        let res = self.post::<_, String>("/v1/auth/token/create", Some(&body), None)?;
        let vault_res: VaultResponse<()> = parse_vault_response(res)?;
        vault_res
            .auth
            .ok_or_else(|| Error::Vault("Created token did not include auth data".into()))
    }

    ///
    /// Saves a secret
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    /// let res = client.set_secret("hello_set", "world");
    /// assert!(res.is_ok());
    /// ```
    pub fn set_secret<S1: Into<String>, S2: AsRef<str>>(&self, key: S1, value: S2) -> Result<()> {
        let secret = DefaultSecretType {
            value: value.as_ref(),
        };
        self.set_custom_secret(key, &secret)
    }

    /// Saves a secret
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    /// use serde::{Deserialize, Serialize};
    ///
    /// #[derive(Deserialize, Serialize)]
    /// struct MyThing {
    ///   awesome: String,
    ///   thing: String,
    /// }
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    /// let secret = MyThing {
    ///   awesome: "I really am cool".into(),
    ///   thing: "this is also in the secret".into(),
    /// };
    /// let res = client.set_custom_secret("hello_set", &secret);
    /// assert!(res.is_ok());
    /// ```
    pub fn set_custom_secret<S1, S2>(&self, secret_name: S1, secret: &S2) -> Result<()>
    where
        S1: Into<String>,
        S2: Serialize,
    {
        let endpoint = match self.secrets_engine {
            SecretsEngine::KVV1 => format!("/v1/{}/{}", self.secret_backend, secret_name.into()),
            SecretsEngine::KVV2 => format!("/v1/{}/data/{}", self.secret_backend, secret_name.into()),
        };
        let json = match self.secrets_engine {
            SecretsEngine::KVV1 => {
                serde_json::to_string(&secret)?
            },
            SecretsEngine::KVV2 => {
                serde_json::to_string(&SecretContainer { data: secret })?
            },
        };
        let _ = self.put::<_, String>(&endpoint, Some(&json), None)?;
        Ok(())
    }

    ///
    /// List secrets at specified path
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    /// let res = client.set_secret("hello/fred", "world");
    /// assert!(res.is_ok());
    /// let res = client.set_secret("hello/bob", "world");
    /// assert!(res.is_ok());
    /// let res = client.list_secrets("hello/");
    /// assert!(res.is_ok());
    /// assert_eq!(res.unwrap(), ["bob", "fred"]);
    /// ```
    pub fn list_secrets<S: AsRef<str>>(&self, key: S) -> Result<Vec<String>> {
        let _namespace_prefix = self.namespace.as_deref().unwrap_or_default();
        let endpoint = match self.secrets_engine {
            SecretsEngine::KVV1 => format!("/v1/{}/{}", self.secret_backend, key.as_ref()),
            SecretsEngine::KVV2 => format!("/v1/{}/metadata/{}", self.secret_backend, key.as_ref()),
        };
        let res = self.list::<_, String>(
            &endpoint,
            None,
            None,
        )?;
        let decoded: VaultResponse<ListResponse> = parse_vault_response(res)?;
        match decoded.data {
            Some(data) => Ok(data.keys),
            _ => Err(Error::Vault(format!(
                "No secrets found in response: `{:#?}`",
                decoded
            ))),
        }
    }

    ///
    /// Fetches a saved secret
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    /// let res = client.set_secret("hello_get", "world");
    /// assert!(res.is_ok());
    /// let res = client.get_secret("hello_get");
    /// assert!(res.is_ok());
    /// assert_eq!(res.unwrap(), "world");
    /// ```
    pub fn get_secret<S: AsRef<str>>(&self, key: S) -> Result<String> {
        let secret: DefaultSecretType<String> = self.get_custom_secret(key)?;
        Ok(secret.value)
    }

    ///
    /// Fetches a saved secret
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    /// use serde::{Deserialize, Serialize};
    ///
    /// #[derive(Debug, Deserialize, Serialize)]
    /// struct MyThing {
    ///   awesome: String,
    ///   thing: String,
    /// }
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    /// let secret = MyThing {
    ///   awesome: "I really am cool".into(),
    ///   thing: "this is also in the secret".into(),
    /// };
    /// let res1 = client.set_custom_secret("custom_secret", &secret);
    /// assert!(res1.is_ok());
    /// let res2: Result<MyThing, _> = client.get_custom_secret("custom_secret");
    /// assert!(res2.is_ok());
    /// let thing = res2.unwrap();
    /// assert_eq!(thing.awesome, "I really am cool");
    /// assert_eq!(thing.thing, "this is also in the secret");
    /// ```
    pub fn get_custom_secret<S: AsRef<str>, S2: DeserializeOwned + fmt::Debug>(
        &self,
        secret_name: S,
    ) -> Result<S2> {
        let endpoint = match self.secrets_engine {
            SecretsEngine::KVV1 => format!("/v1/{}/{}", self.secret_backend, secret_name.as_ref()),
            SecretsEngine::KVV2 => format!("/v1/{}/data/{}", self.secret_backend, secret_name.as_ref()),
        };
        let res = self.get::<_, String>(&endpoint, None)?;
        match self.secrets_engine {
            SecretsEngine::KVV1 => {
                let decoded: VaultResponse<S2> = parse_vault_response(res)?;
                match decoded.data {
                    Some(data) => Ok(data),
                    _ => Err(Error::Vault(format!("No secret found in response: `{:#?}`", decoded))),
                }
            },
            SecretsEngine::KVV2 => {
                let decoded: VaultResponse<SecretDataWrapper<S2>> = parse_vault_response(res)?;
                match decoded.data {
                    Some(data) => Ok(data.data),
                    _ => Err(Error::Vault(format!("No secret found in response: `{:#?}`", decoded))),
                }
            },
        }
    }

    /// Fetch a wrapped secret. Token (one-time use) to fetch secret will be in `wrap_info.token`
    /// https://www.vaultproject.io/docs/secrets/cubbyhole/index.html
    pub fn get_secret_wrapped<S1: AsRef<str>, S2: AsRef<str>>(
        &self,
        key: S1,
        wrap_ttl: S2,
    ) -> Result<VaultResponse<()>> {
        let res = self.get(
            &format!("/v1/{}/data/{}", self.secret_backend, key.as_ref())[..],
            Some(wrap_ttl.as_ref()),
        )?;
        parse_vault_response(res)
    }

    /// Using a vault client created from a wrapping token, fetch the unwrapped `VaultResponse` from
    /// `sys/wrapping/unwrap`.
    ///
    /// The `data` attribute of `VaultResponse` should contain the unwrapped information, which is
    /// returned as a `HashMap<String, String>`.
    pub fn get_unwrapped_response(&self) -> Result<VaultResponse<HashMap<String, String>>> {
        let res = self.post::<_, String>("/v1/sys/wrapping/unwrap", None, None)?;
        let result: VaultResponse<SecretDataWrapper<HashMap<String, String>>> =
            parse_vault_response(res)?;
        Ok(result.into())
    }

    /// Reads the properties of an existing `AppRole`.
    pub fn get_app_role_properties<S: AsRef<str>>(
        &self,
        role_name: S,
    ) -> Result<VaultResponse<AppRoleProperties>> {
        let res = self.get::<_, String>(
            &format!("/v1/auth/approle/role/{}", role_name.as_ref()),
            None,
        )?;
        parse_vault_response(res)
    }

    /// Encrypt a plaintext via Transit secret backend.
    ///
    /// # Example
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    /// let res = client.transit_encrypt(None, "keyname", b"plaintext");
    /// ```
    pub fn transit_encrypt<S1: Into<String>, S2: AsRef<[u8]>>(
        &self,
        mountpoint: Option<String>,
        key: S1,
        plaintext: S2,
    ) -> Result<Vec<u8>> {
        let path = mountpoint.unwrap_or_else(|| "transit".to_owned());
        let encoded_plaintext = base64::encode(plaintext.as_ref());
        let res = self.post::<_, String>(
            &format!("/v1/{}/encrypt/{}", path, key.into())[..],
            Some(&format!("{{\"plaintext\": \"{}\"}}", encoded_plaintext)[..]),
            None,
        )?;
        let decoded: VaultResponse<TransitEncryptedData> = parse_vault_response(res)?;
        let payload = match decoded.data {
            Some(data) => data.ciphertext,
            _ => {
                return Err(Error::Vault(format!(
                    "No ciphertext found in response: `{:#?}`",
                    decoded
                )))
            }
        };
        if !payload.starts_with("vault:v1:") {
            return Err(Error::Vault(format!(
                "Unrecognized ciphertext format: `{:#?}`",
                payload
            )));
        };
        let encoded_ciphertext = payload.trim_start_matches("vault:v1:");
        let encrypted = base64::decode(encoded_ciphertext)?;
        Ok(encrypted)
    }

    /// Decrypt a ciphertext via Transit secret backend.
    ///
    /// # Example
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    /// let res = client.transit_decrypt(None, "keyname", b"\x02af\x61bcb\x55d");
    /// ```
    pub fn transit_decrypt<S1: Into<String>, S2: AsRef<[u8]>>(
        &self,
        mountpoint: Option<String>,
        key: S1,
        ciphertext: S2,
    ) -> Result<Vec<u8>> {
        let path = mountpoint.unwrap_or_else(|| "transit".to_owned());
        let encoded_ciphertext = "vault:v1:".to_owned() + &base64::encode(ciphertext.as_ref());
        let res = self.post::<_, String>(
            &format!("/v1/{}/decrypt/{}", path, key.into())[..],
            Some(&format!("{{\"ciphertext\": \"{}\"}}", encoded_ciphertext)[..]),
            None,
        )?;
        let decoded: VaultResponse<TransitDecryptedData> = parse_vault_response(res)?;
        let decrypted = match decoded.data {
            Some(data) => data.plaintext,
            _ => {
                return Err(Error::Vault(format!(
                    "No plaintext found in response: `{:#?}`",
                    decoded
                )))
            }
        };
        let plaintext = base64::decode(&decrypted)?;
        Ok(plaintext)
    }

    /// This function is an "escape hatch" of sorts to call any other vault api methods that
    /// aren't directly supported in this library.
    ///
    /// Select the http verb you want, along with the endpoint, e.g. `auth/token/create`, along
    /// with any wrapping or associated body text and the request will be sent.
    ///
    /// See `it_can_perform_approle_workflow` test case for examples.
    pub fn call_endpoint<D: DeserializeOwned>(
        &self,
        http_verb: HttpVerb,
        endpoint: &str,
        wrap_ttl: Option<&str>,
        body: Option<&str>,
    ) -> Result<EndpointResponse<D>> {
        let url = format!("/v1/{}", endpoint);
        match http_verb {
            HttpVerb::GET => {
                let mut res = self.get(&url, wrap_ttl)?;
                parse_endpoint_response(&mut res)
            }
            HttpVerb::POST => {
                let mut res = self.post(&url, body, wrap_ttl)?;
                parse_endpoint_response(&mut res)
            }
            HttpVerb::PUT => {
                let mut res = self.put(&url, body, wrap_ttl)?;
                parse_endpoint_response(&mut res)
            }
            HttpVerb::DELETE => {
                let mut res = self.delete(&url)?;
                parse_endpoint_response(&mut res)
            }
            HttpVerb::LIST => {
                let mut res = self.list(&url, body, wrap_ttl)?;
                parse_endpoint_response(&mut res)
            }
        }
    }

    /// Accesses a given endpoint using the provided `wrap_ttl` and returns a single-use
    /// `wrapping_token` to access the response provided by the endpoint.
    pub fn get_wrapping_token_for_endpoint(
        &self,
        http_verb: HttpVerb,
        endpoint: &str,
        wrap_ttl: &str,
        body: Option<&str>,
    ) -> Result<String> {
        let res = self.call_endpoint::<()>(http_verb, endpoint.as_ref(), Some(wrap_ttl), body)?;
        match res {
            EndpointResponse::VaultResponse(res) => match res.wrap_info {
                Some(wrap_info) => Ok(wrap_info.token),
                _ => Err(Error::Vault(format!(
                    "wrap_info is missing in response: {:?}",
                    res
                ))),
            },
            EndpointResponse::Empty => Err(Error::Vault("Received an empty response".to_string())),
        }
    }

    ///
    /// Deletes a saved secret
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    /// let res = client.set_secret("hello_delete", "world");
    /// assert!(res.is_ok());
    /// let res = client.delete_secret("hello_delete");
    /// assert!(res.is_ok());
    /// ```
    pub fn delete_secret(&self, key: &str) -> Result<()> {
        let _ = match self.secrets_engine {
            SecretsEngine::KVV1 => self.delete(&format!("/v1/{}/{}", self.secret_backend, key)[..])?,
            SecretsEngine::KVV2 => self.delete(&format!("/v1/{}/data/{}", self.secret_backend, key)[..])?,
        };
        Ok(())
    }

    /// Get postgresql secret backend
    /// https://www.vaultproject.io/docs/secrets/postgresql/index.html
    pub fn get_postgresql_backend(&self, name: &str) -> Result<VaultResponse<PostgresqlLogin>> {
        self.get_secret_engine_creds("postgresql", name)
    }

    /// Get creds from an arbitrary backend
    /// ```no_run
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    /// use serde::Deserialize;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    ///
    /// #[derive(Deserialize)]
    /// struct PacketKey {
    ///   api_key_token: String,
    /// }
    ///
    /// let res = client.get_secret_engine_creds::<PacketKey>("packet", "1h-read-only-user").unwrap();
    /// let api_token = res.data.unwrap().api_key_token;
    /// ```
    pub fn get_secret_engine_creds<K>(&self, backend: &str, name: &str) -> Result<VaultResponse<K>>
    where
        K: DeserializeOwned,
    {
        let res = self.get::<_, String>(&format!("/v1/{}/creds/{}", backend, name)[..], None)?;
        let decoded: VaultResponse<K> = parse_vault_response(res)?;
        Ok(decoded)
    }

    /// Get a list of policy names defined by this vault.  This requires
    /// `root` privileges. Corresponds to [`/sys/policy`][/sys/policy].
    ///
    /// ```
    /// # extern crate hashicorp_vault as vault;
    /// # use vault::Client;
    ///
    /// let host = "http://127.0.0.1:8200";
    /// let token = "test12345";
    /// let namespace: Option<String> = None;
    /// let client = Client::new(host, token, namespace).unwrap();
    ///
    /// let res = client.policies().unwrap();
    /// assert!(res.contains(&"root".to_owned()));
    /// ```
    ///
    /// [/sys/policy]: https://www.vaultproject.io/docs/http/sys-policy.html
    pub fn policies(&self) -> Result<Vec<String>> {
        let res = self.get::<_, String>("/v1/sys/policy", None)?;
        let decoded: PoliciesResponse = parse_vault_response(res)?;
        Ok(decoded.policies)
    }

    fn get<S1: AsRef<str>, S2: Into<String>>(
        &self,
        endpoint: S1,
        wrap_ttl: Option<S2>,
    ) -> Result<Response> {
        let h = self.host.join(endpoint.as_ref())?;
        let mut request = self.client.request(Method::GET, h)
            .header("X-Vault-Token", self.token.to_string())
            .header(CONTENT_TYPE, "application/json");

        if let Some(namespace) = &self.namespace {
            request = request.header("X-Vault-Namespace", namespace);
        }

        match wrap_ttl {
            Some(wrap_ttl) => Ok(handle_reqwest_response(
                request.header("X-Vault-Wrap-TTL", wrap_ttl.into()).send(),
            )?),
            None => Ok(handle_reqwest_response(request.send())?),
        }
    }

    fn delete<S: AsRef<str>>(&self, endpoint: S) -> Result<Response> {
        let mut request = self.client
            .request(Method::DELETE, self.host.join(endpoint.as_ref())?)
            .header("X-Vault-Token", self.token.to_string())
            .header(CONTENT_TYPE, "application/json");

        if let Some(namespace) = &self.namespace {
            request = request.header("X-Vault-Namespace", namespace);
        }

        Ok(handle_reqwest_response(request.send())?)
    }

    fn post<S1: AsRef<str>, S2: Into<String>>(
        &self,
        endpoint: S1,
        body: Option<&str>,
        wrap_ttl: Option<S2>,
    ) -> Result<Response> {
        let h = self.host.join(endpoint.as_ref())?;
        let body = body.unwrap_or("").to_string();
        let mut request = self.client
            .request(Method::POST, h)
            .header("X-Vault-Token", self.token.to_string())
            .header(CONTENT_TYPE, "application/json");

        if let Some(namespace) = &self.namespace {
            request = request.header("X-Vault-Namespace", namespace);
        }

        match wrap_ttl {
            Some(wrap_ttl) => Ok(handle_reqwest_response(
                request
                    .header("X-Vault-Wrap-TTL", wrap_ttl.into())
                    .body(body)
                    .send(),
            )?),
            None => Ok(handle_reqwest_response(
                request.body(body).send(),
            )?),
        }
    }

    fn put<S1: AsRef<str>, S2: Into<String>>(
        &self,
        endpoint: S1,
        body: Option<&str>,
        wrap_ttl: Option<S2>,
    ) -> Result<Response> {
        let h = self.host.join(endpoint.as_ref())?;
        let body = if let Some(body) = body {
            body.to_string()
        } else {
            String::new()
        };
        let mut request = self.client
            .request(Method::PUT, h)
            .header("X-Vault-Token", self.token.to_string())
            .header(CONTENT_TYPE, "application/json");

        if let Some(namespace) = &self.namespace {
            request = request.header("X-Vault-Namespace", namespace);
        }

        match wrap_ttl {
            Some(wrap_ttl) => Ok(handle_reqwest_response(
                request
                    .header("X-Vault-Wrap-TTL", wrap_ttl.into())
                    .body(body)
                    .send(),
            )?),
            None => Ok(handle_reqwest_response(
                request.body(body).send(),
            )?),
        }
    }

    fn list<S1: AsRef<str>, S2: Into<String>>(
        &self,
        endpoint: S1,
        body: Option<&str>,
        wrap_ttl: Option<S2>,
    ) -> Result<Response> {
        let h = self.host.join(endpoint.as_ref())?;
        let body = if let Some(body) = body {
            body.to_string()
        } else {
            String::new()
        };
        let mut request = self.client
            .request(
                Method::from_str("LIST").expect("Failed to parse LIST to Method"),
                h,
            )
            .header("X-Vault-Token", self.token.to_string())
            .header(CONTENT_TYPE, "application/json");

        if let Some(namespace) = &self.namespace {
            request = request.header("X-Vault-Namespace", namespace);
        }

        match wrap_ttl {
            Some(wrap_ttl) => Ok(handle_reqwest_response(
                request
                    .header("X-Vault-Wrap-TTL", wrap_ttl.into())
                    .body(body)
                    .send(),
            )?),
            None => Ok(handle_reqwest_response(
                request.body(body).send(),
            )?),
        }
    }
}

/// helper fn to check `Response` for success
fn handle_reqwest_response(res: StdResult<Response, reqwest::Error>) -> Result<Response> {
    let mut res = res?;
    if res.status().is_success() {
        Ok(res)
    } else {
        let mut error_msg = String::new();
        let _ = res.read_to_string(&mut error_msg).unwrap_or({
            error_msg.push_str("Could not read vault response.");
            0
        });
        Err(Error::VaultResponse(
            format!(
                "Vault request failed: {:?}, error message: `{}`",
                res, error_msg
            ),
            res,
        ))
    }
}

///
/// Parse a vault response manually
///
/// ```
/// # extern crate hashicorp_vault as vault;
/// # use vault::Client;
/// use std::io::Read;
/// use vault::{Error, Result, client::{VaultResponse, SecretDataWrapper}, TryInto};
/// use std::result::Result as StdResult;
/// use reqwest::{
///    blocking::{Client as ReqwestClient, Response},
///    header::CONTENT_TYPE,
///    Method,
///  };
/// use serde::{Deserialize, Serialize};
/// use url::Url;
///
/// #[derive(Debug, Deserialize, Serialize)]
/// struct MyThing {
///   awesome: String,
///   thing: String,
/// }
///
/// fn handle_reqwest_response(res: StdResult<Response, reqwest::Error>) -> Result<Response> {
///     let mut res = res?;
///     if res.status().is_success() {
///         Ok(res)
///     } else {
///         let mut error_msg = String::new();
///         let _ = res.read_to_string(&mut error_msg).unwrap_or({
///             error_msg.push_str("Could not read vault response.");
///             0
///         });
///         Err(Error::VaultResponse(
///             format!(
///                 "Vault request failed: {:?}, error message: `{}`",
///                 res, error_msg
///             ),
///             res,
///         ))
///     }
/// }
/// fn get<S1: AsRef<str>, S2: Into<String>, U: TryInto<Url, Err = Error>>(
///     host: U,
///     token: &str,
///     endpoint: S1,
///     wrap_ttl: Option<S2>,
/// ) -> Result<Response> {
/// let host = host.try_into()?;
///     let h = host.join(endpoint.as_ref())?;
///     let client = ReqwestClient::new();;
///     match wrap_ttl {
///         Some(wrap_ttl) => Ok(handle_reqwest_response(
///             client
///                 .request(Method::GET, h)
///                 .header("X-Vault-Token", token.to_string())
///                 .header(CONTENT_TYPE, "application/json")
///                 .header("X-Vault-Wrap-TTL", wrap_ttl.into())
///                 .send(),
///         )?),
///         None => Ok(handle_reqwest_response(
///             client
///                 .request(Method::GET, h)
///                 .header("X-Vault-Token", token.to_string())
///                 .header(CONTENT_TYPE, "application/json")
///                 .send(),
///         )?),
///     }
/// }
/// let host = "http://127.0.0.1:8200";
/// let token = "test12345";
/// let namespace: Option<String> = None;
/// let client = Client::new(host, token, namespace).unwrap();
/// let secret = MyThing {
///   awesome: "I really am cool".into(),
///   thing: "this is also in the secret".into(),
/// };
/// let res1 = client.set_custom_secret("custom_secret", &secret);
/// assert!(res1.is_ok());
/// let res = get::<&str, &str, &str>(
///     host,
///     token,
///     "/v1/secret/data/custom_secret",
///     None,
/// ).unwrap();
/// let decoded: VaultResponse<SecretDataWrapper<MyThing>> = vault::client::parse_vault_response(res).unwrap();
/// let res2 = match decoded.data {
///     Some(data) => Ok(data.data),
///     _ => Err(Error::Vault(format!(
///         "No secret found in response: `{:#?}`",
///         decoded
///     ))),
/// };
/// assert!(res2.is_ok());
/// let thing = res2.unwrap();
/// assert_eq!(thing.awesome, "I really am cool");
/// assert_eq!(thing.thing, "this is also in the secret");
pub fn parse_vault_response<T>(res: Response) -> Result<T>
where
    T: DeserializeOwned,
{
    trace!("Response: {:?}", &res);
    Ok(serde_json::from_reader(res)?)
}

/// checks if response is empty before attempting to convert to a `VaultResponse`
fn parse_endpoint_response<T>(res: &mut Response) -> Result<EndpointResponse<T>>
where
    T: DeserializeOwned,
{
    let mut body = String::new();
    let _ = res.read_to_string(&mut body)?;
    trace!("Response: {:?}", &body);
    if body.is_empty() {
        Ok(EndpointResponse::Empty)
    } else {
        Ok(EndpointResponse::VaultResponse(serde_json::from_str(
            &body,
        )?))
    }
}
0707010000002C000081A4000000000000000000000001681565810000369E000000000000000000000000000000000000002600000000vault-sync-0.11.0/vault-rs/src/lib.rs#![deny(
    missing_docs,
    missing_debug_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unstable_features,
    unused_import_braces,
    unused_qualifications,
    unused_results
)]
#![cfg_attr(test, deny(warnings))]
#![cfg_attr(feature = "clippy", allow(unstable_features))]
#![cfg_attr(feature = "clippy", feature(plugin))]
#![cfg_attr(feature = "clippy", plugin(clippy))]
#![cfg_attr(feature = "clippy", deny(clippy))]

//! Client API for interacting with [Vault](https://www.vaultproject.io/docs/http/index.html)

extern crate base64;
extern crate reqwest;
#[macro_use]
extern crate log;
#[macro_use]
extern crate quick_error;
pub extern crate chrono;
extern crate serde;
pub extern crate url;

/// vault client
pub mod client;
pub use crate::client::error::{Error, Result};
pub use crate::client::VaultClient as Client;
use url::Url;

/// Waiting to stabilize: https://github.com/rust-lang/rust/issues/33417
///
/// An attempted conversion that consumes `self`, which may or may not be expensive.
///
/// Library authors should not directly implement this trait, but should prefer implementing
/// the [`TryFrom`] trait, which offers greater flexibility and provides an equivalent `TryInto`
/// implementation for free, thanks to a blanket implementation in the standard library.
///
/// [`TryFrom`]: trait.TryFrom.html
pub trait TryInto<T>: Sized {
    /// The type returned in the event of a conversion error.
    type Err;

    /// Performs the conversion.
    fn try_into(self) -> ::std::result::Result<T, Self::Err>;
}

/// Waiting to stabilize: https://github.com/rust-lang/rust/issues/33417
///
/// Attempt to construct `Self` via a conversion.
pub trait TryFrom<T>: Sized {
    /// The type returned in the event of a conversion error.
    type Err;

    /// Performs the conversion.
    fn try_from(_: T) -> ::std::result::Result<Self, Self::Err>;
}

impl<T, U> TryInto<U> for T
where
    U: TryFrom<T>,
{
    type Err = U::Err;

    fn try_into(self) -> ::std::result::Result<U, U::Err> {
        U::try_from(self)
    }
}

impl TryFrom<Url> for Url {
    type Err = Error;
    fn try_from(u: Url) -> ::std::result::Result<Self, Self::Err> {
        Ok(u)
    }
}

impl<'a> TryFrom<&'a Url> for Url {
    type Err = Error;
    fn try_from(u: &Url) -> ::std::result::Result<Self, Self::Err> {
        Ok(u.clone())
    }
}

impl<'a> TryFrom<&'a str> for Url {
    type Err = Error;
    fn try_from(s: &str) -> ::std::result::Result<Self, Self::Err> {
        match Url::parse(s) {
            Ok(u) => Ok(u),
            Err(e) => Err(e.into()),
        }
    }
}

impl<'a> TryFrom<&'a String> for Url {
    type Err = Error;
    fn try_from(s: &String) -> ::std::result::Result<Self, Self::Err> {
        match Url::parse(s) {
            Ok(u) => Ok(u),
            Err(e) => Err(e.into()),
        }
    }
}

impl TryFrom<String> for Url {
    type Err = Error;
    fn try_from(s: String) -> ::std::result::Result<Self, Self::Err> {
        match Url::parse(&s) {
            Ok(u) => Ok(u),
            Err(e) => Err(e.into()),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::client::HttpVerb::*;
    use crate::client::VaultClient as Client;
    use crate::client::{self, EndpointResponse};
    use crate::Error;
    use reqwest::StatusCode;
    use serde::{Deserialize, Serialize};
    use serde_json::Value;

    /// vault host for testing
    const HOST: &str = "http://127.0.0.1:8200";
    /// root token needed for testing
    const TOKEN: &str = "test12345";

    #[test]
    fn it_can_create_a_client() {
        let _ = Client::new(HOST, TOKEN).unwrap();
    }

    #[test]
    fn it_can_create_a_client_from_a_string_reference() {
        let _ = Client::new(&HOST.to_string(), TOKEN).unwrap();
    }

    #[test]
    fn it_can_create_a_client_from_a_string() {
        let _ = Client::new(HOST.to_string(), TOKEN).unwrap();
    }

    #[test]
    fn it_can_query_secrets() {
        let client = Client::new(HOST, TOKEN).unwrap();
        let res = client.set_secret("hello_query", "world");
        assert!(res.is_ok());
        let res = client.get_secret("hello_query").unwrap();
        assert_eq!(res, "world");
    }

    #[test]
    fn it_can_store_json_secrets() {
        let client = Client::new(HOST, TOKEN).unwrap();
        let json = "{\"foo\": {\"bar\": [\"baz\"]}}";
        let res = client.set_secret("json_secret", json);
        assert!(res.is_ok());
        let res = client.get_secret("json_secret").unwrap();
        assert_eq!(res, json)
    }

    #[test]
    fn it_can_list_secrets() {
        let client = Client::new(HOST, TOKEN).unwrap();

        let _res = client.set_secret("hello/fred", "world").unwrap();
        // assert!(res.is_ok());
        let res = client.set_secret("hello/bob", "world");
        assert!(res.is_ok());

        let res = client.list_secrets("hello");
        assert!(res.is_ok());
        assert_eq!(res.unwrap(), ["bob", "fred"]);

        let res = client.list_secrets("hello/");
        assert!(res.is_ok());
        assert_eq!(res.unwrap(), ["bob", "fred"]);
    }

    #[test]
    fn it_can_detect_404_status() {
        let client = Client::new(HOST, TOKEN).unwrap();

        let res = client.list_secrets("non/existent/key");
        assert!(res.is_err());

        if let Err(Error::VaultResponse(_, response)) = res {
            assert_eq!(response.status(), StatusCode::NOT_FOUND);
        } else {
            panic!("Error should match on VaultResponse with reqwest response.");
        }
    }

    #[test]
    fn it_can_write_secrets_with_newline() {
        let client = Client::new(HOST, TOKEN).unwrap();

        let res = client.set_secret("hello_set", "world\n");
        assert!(res.is_ok());
        let res = client.get_secret("hello_set").unwrap();
        assert_eq!(res, "world\n");
    }

    #[test]
    fn it_returns_err_on_forbidden() {
        let client = Client::new(HOST, "test123456");
        // assert_eq!(Err("Forbidden".to_string()), client);
        assert!(client.is_err());
    }

    #[test]
    fn it_can_delete_a_secret() {
        let client = Client::new(HOST, TOKEN).unwrap();

        let res = client.set_secret("hello_delete", "world");
        assert!(res.is_ok());
        let res = client.get_secret("hello_delete").unwrap();
        assert_eq!(res, "world");
        let res = client.delete_secret("hello_delete");
        assert!(res.is_ok());
        let res = client.get_secret("hello_delete");
        assert!(res.is_err());
    }

    #[test]
    fn it_can_perform_approle_workflow() {
        use std::collections::HashMap;

        let c = Client::new(HOST, TOKEN).unwrap();
        let mut body = "{\"type\":\"approle\"}";
        // Ensure we do not currently have an approle backend enabled.
        // Older vault versions (<1.2.0) seem to have an AppRole backend
        // enabled by default, so calling the POST to create a new one
        // fails with a 400 status
        let _: EndpointResponse<()> = c
            .call_endpoint(DELETE, "sys/auth/approle", None, None)
            .unwrap();
        // enable approle auth backend
        let mut res: EndpointResponse<()> = c
            .call_endpoint(POST, "sys/auth/approle", None, Some(body))
            .unwrap();
        panic_non_empty(&res);
        // make a new approle
        body = "{\"secret_id_ttl\":\"10m\", \"token_ttl\":\"20m\", \"token_max_ttl\":\"30m\", \
                \"secret_id_num_uses\":40}";
        res = c
            .call_endpoint(POST, "auth/approle/role/test_role", None, Some(body))
            .unwrap();
        panic_non_empty(&res);

        // let's test the properties endpoint while we're here
        let _ = c.get_app_role_properties("test_role").unwrap();

        // get approle's role-id
        let res: EndpointResponse<HashMap<String, String>> = c
            .call_endpoint(GET, "auth/approle/role/test_role/role-id", None, None)
            .unwrap();
        let data = match res {
            EndpointResponse::VaultResponse(res) => res.data.unwrap(),
            _ => panic!("expected vault response, got: {:?}", res),
        };
        let role_id = &data["role_id"];
        assert!(!role_id.is_empty());

        // now get a secret id for this approle
        let res: EndpointResponse<HashMap<String, Value>> = c
            .call_endpoint(POST, "auth/approle/role/test_role/secret-id", None, None)
            .unwrap();
        let data = match res {
            EndpointResponse::VaultResponse(res) => res.data.unwrap(),
            _ => panic!("expected vault response, got: {:?}", res),
        };
        let secret_id = &data["secret_id"].as_str().unwrap();

        // now finally we can try to actually login!
        let _ = Client::new_app_role(HOST, &role_id[..], Some(&secret_id[..])).unwrap();

        // clean up by disabling approle auth backend
        let res = c
            .call_endpoint(DELETE, "sys/auth/approle", None, None)
            .unwrap();
        panic_non_empty(&res);
    }

    #[test]
    fn it_can_read_a_wrapped_secret() {
        let client = Client::new(HOST, TOKEN).unwrap();
        let res = client.set_secret("hello_delete_2", "second world");
        assert!(res.is_ok());
        // wrap the secret's value in `sys/wrapping/unwrap` with a TTL of 2 minutes
        let res = client.get_secret_wrapped("hello_delete_2", "2m").unwrap();
        let wrapping_token = res.wrap_info.unwrap().token;
        // make a new client with the wrapping token
        let c2 = Client::new_no_lookup(HOST, wrapping_token).unwrap();
        // read the cubbyhole response (can only do this once!)
        let res = c2.get_unwrapped_response().unwrap();
        assert_eq!(res.data.unwrap()["value"], "second world");
    }

    #[test]
    fn it_can_store_policies() {
        // use trailing slash for host to ensure Url processing fixes this later
        let c = Client::new("http://127.0.0.1:8200/", TOKEN).unwrap();
        let body = "{\"policy\":\"{}\"}";
        // enable approle auth backend
        let res: EndpointResponse<()> = c
            .call_endpoint(PUT, "sys/policy/test_policy_1", None, Some(body))
            .unwrap();
        panic_non_empty(&res);
        let res: EndpointResponse<()> = c
            .call_endpoint(PUT, "sys/policy/test_policy_2", None, Some(body))
            .unwrap();
        panic_non_empty(&res);
        let client_policies = c.policies().unwrap();
        let expected_policies = ["default", "test_policy_1", "test_policy_2", "root"];
        let _ = expected_policies
            .iter()
            .map(|p| {
                assert!(client_policies.contains(&(*p).to_string()));
            })
            .last();
        let token_name = "policy_test_token".to_string();
        let token_opts = client::TokenOptions::default()
            .policies(vec!["test_policy_1", "test_policy_2"].into_iter())
            .default_policy(false)
            .id(&token_name[..])
            .ttl(client::VaultDuration::minutes(1));
        let _ = c.create_token(&token_opts).unwrap();
        let body = format!("{{\"token\":\"{}\"}}", &token_name);
        let res: EndpointResponse<client::TokenData> = c
            .call_endpoint(POST, "auth/token/lookup", None, Some(&body))
            .unwrap();
        match res {
            EndpointResponse::VaultResponse(res) => {
                let data = res.data.unwrap();
                let mut policies = data.policies;
                policies.sort();
                assert_eq!(policies, ["test_policy_1", "test_policy_2"]);
            }
            _ => panic!("expected vault response, got: {:?}", res),
        }
        // clean-up
        let res: EndpointResponse<()> = c
            .call_endpoint(DELETE, "sys/policy/test_policy_1", None, None)
            .unwrap();
        panic_non_empty(&res);
        let res: EndpointResponse<()> = c
            .call_endpoint(DELETE, "sys/policy/test_policy_2", None, None)
            .unwrap();
        panic_non_empty(&res);
    }

    #[test]
    fn it_can_list_things() {
        let c = Client::new(HOST, TOKEN).unwrap();
        let _ = c
            .create_token(&client::TokenOptions::default().ttl(client::VaultDuration::minutes(1)))
            .unwrap();
        let res: EndpointResponse<client::ListResponse> = c
            .call_endpoint(LIST, "auth/token/accessors", None, None)
            .unwrap();
        match res {
            EndpointResponse::VaultResponse(res) => {
                let data = res.data.unwrap();
                assert!(data.keys.len() > 2);
            }
            _ => panic!("expected vault response, got: {:?}", res),
        }
    }

    #[test]
    fn it_can_encrypt_decrypt_transit() {
        let key_id = "test-vault-rs";
        let plaintext = b"data\0to\0encrypt";

        let client = Client::new(HOST, TOKEN).unwrap();
        let enc_resp = client.transit_encrypt(None, key_id, plaintext);
        let encrypted = enc_resp.unwrap();
        let dec_resp = client.transit_decrypt(None, key_id, encrypted);
        let payload = dec_resp.unwrap();
        assert_eq!(plaintext, payload.as_slice());
    }

    // helper fn to panic on empty responses
    fn panic_non_empty(res: &EndpointResponse<()>) {
        match *res {
            EndpointResponse::Empty => {}
            _ => panic!("expected empty response, received: {:?}", res),
        }
    }

    #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
    struct CustomSecretType {
        name: String,
    }

    #[test]
    fn it_can_set_and_get_a_custom_secret_type() {
        let input = CustomSecretType {
            name: "test".into(),
        };

        let client = Client::new(HOST, TOKEN).unwrap();

        let res = client.set_custom_secret("custom_type", &input);
        assert!(res.is_ok());
        let res: CustomSecretType = client.get_custom_secret("custom_type").unwrap();
        assert_eq!(res, input);
    }
}
0707010000002D000081A40000000000000000000000016815658100000C70000000000000000000000000000000000000002A00000000vault-sync-0.11.0/vault-sync.example.yaml# Configuration file for vault-sync
# https://github.com/pbchekin/vault-sync

# Name for this vault-sync instance. If there are multiple vault-sync instances running for the same
# source Vault, then this name must be unique for each instance.
id: vault-sync

# Time between full syncs. The full sync usually runs when vault-sync starts, then vault-sync only
# apply changes for the secrets. However, vault-sync also does the full sync every this interval.
# It does not do any changes to the destination, if the source secrets are not changed.
full_sync_interval: 3600 # 1h

# Optional address and port for this vault-sync to listen for the Vault audit log. Set this if you
# are planning to use the Vault audit device.
# bind: 0.0.0.0:8202

# Source Vault configuration to sync secrets from.
src:
  # Vault URL
  url: http://127.0.0.1:8200/

  # Prefix for secrets: only secrets with path starting from this prefix will be synchronized with
  # the target Vault. Use empty string ("") for all secrets.
  prefix: ""

  # Vault namespace, not set by default.
  # namespace: null

  # Path for the secrets engine. For multiple backends use "backends" with a list.
  # Default is single backend "secret".
  # backend: secret
  #  or
  # backends:
  #   - secret1
  #   - secret2

  # Secrets engine version, default is 2.
  # version: 2

  # Vault Token auth method
  # Set token (or environment variable VAULT_SYNC_SRC_TOKEN)
  # token: ***
  # token_ttl: 86400 # optional, 12h

  # Vault AppRole auth method
  # Set role_id and secret_id (or environment variables VAULT_SYNC_SRC_ROLE_ID and VAULT_SYNC_SRC_SECRET_ID)
  # role_id: ***
  # secret_id: ***
  # token_ttl: 86400 # optional, 12h
  # token_max_ttl: 2764800 # 32d

# Destination Vault configuration to sync secrets to.
dst:
  # Vault URL
  url: http://127.0.0.1:8200/

  # Prefix for secrets: this prefix will replace the corresponding prefix from the 'src' section.
  # This allows syncing a tree of secrets to a non overlapping tree in the same Vault.
  # For example: if src.prefix is "src" and dst.prefix is "dst", then secret "src/secret1" will be
  # synced to "dst/secret1".
  prefix: ""

  # Vault namespace, not set by default.
  # namespace: null
  
  # Path for the secrets engine. If "backend" or "backends" not specified for here, then the
  # corresponding configuration for src will be used for dst. Note that currently only the following
  # cases are supported:
  # * one src backend to one dst backend
  # * multiple src backends to the same number of dst backends
  # Other cases (one to many, many to one, or different numbers of src and dst backends) are not
  # currently supported.
  # backend: secret
  #  or
  # backends:
  #   - secret1
  #   - secret2

  # Secrets engine version, default is 2.
  # version: 2

  # Vault Token auth method
  # Set token (or environment variable VAULT_SYNC_DST_TOKEN)
  # token: ***
  # token_ttl: 86400 # optional, 12h

  # Vault AppRole auth method
  # Set role_id and secret_id (or environment variables VAULT_SYNC_DST_ROLE_ID and VAULT_SYNC_DST_SECRET_ID)
  # role_id: ***
  # secret_id: ***
  # token_ttl: 86400 # optional, 12h
  # token_max_ttl: 2764800 # 32d
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!439 blocks
openSUSE Build Service is sponsored by