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