mirror of
https://github.com/bartvdbraak/keyweave.git
synced 2025-04-28 15:21:21 +00:00
Compare commits
No commits in common. "main" and "v0.2.3" have entirely different histories.
15 changed files with 499 additions and 1733 deletions
9
.github/renovate.json
vendored
9
.github/renovate.json
vendored
|
@ -1,12 +1,5 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["config:base"],
|
"extends": ["config:base"],
|
||||||
"reviewers": ["bartvdbraak"],
|
"reviewers": ["bartvdbraak"]
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchPackagePrefixes": ["azure"],
|
|
||||||
"groupName": "Azure Dependencies",
|
|
||||||
"groupSlug": "azure-dependencies"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
4
.github/workflows/checks.yml
vendored
4
.github/workflows/checks.yml
vendored
|
@ -39,8 +39,8 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Run unit tests
|
- name: Run tests
|
||||||
run: cargo test --bins
|
run: cargo test --all
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|
45
.github/workflows/release.yml
vendored
45
.github/workflows/release.yml
vendored
|
@ -70,7 +70,7 @@ jobs:
|
||||||
experimental: false
|
experimental: false
|
||||||
|
|
||||||
- name: mac-arm64
|
- name: mac-arm64
|
||||||
os: macos-latest
|
os: macos-11.0
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
cross: true
|
cross: true
|
||||||
experimental: true
|
experimental: true
|
||||||
|
@ -87,12 +87,12 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/registry
|
path: ~/.cargo/registry
|
||||||
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
|
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
|
||||||
|
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
if: startsWith(matrix.name, 'linux-')
|
if: startsWith(matrix.name, 'linux-')
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/bin
|
path: ~/.cargo/bin
|
||||||
|
@ -104,6 +104,7 @@ jobs:
|
||||||
|
|
||||||
- uses: taiki-e/setup-cross-toolchain-action@v1
|
- uses: taiki-e/setup-cross-toolchain-action@v1
|
||||||
with:
|
with:
|
||||||
|
# NB: sets CARGO_BUILD_TARGET evar - do not need --target flag in build
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|
||||||
- uses: taiki-e/install-action@cross
|
- uses: taiki-e/install-action@cross
|
||||||
|
@ -116,7 +117,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
echo "${{ needs.pre-check.outputs.version }}" > VERSION
|
echo "${{ needs.pre-check.outputs.version }}" > VERSION
|
||||||
|
|
||||||
- name: Archive and Package
|
- name: Package
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
@ -125,19 +126,24 @@ jobs:
|
||||||
bin="target/${{ matrix.target }}/release/keyweave${ext}"
|
bin="target/${{ matrix.target }}/release/keyweave${ext}"
|
||||||
strip "$bin" || true
|
strip "$bin" || true
|
||||||
dst="keyweave-${{ matrix.target }}"
|
dst="keyweave-${{ matrix.target }}"
|
||||||
mkdir -p "$dst" dist
|
mkdir "$dst"
|
||||||
cp "$bin" "$dst/"
|
cp "$bin" "$dst/"
|
||||||
if [[ "${{ matrix.name }}" == windows-* ]] ; then
|
|
||||||
mv "$dst/keyweave${ext}" dist/keyweave-${{ matrix.target }}.exe
|
|
||||||
else
|
|
||||||
tar cavf "$dst.tar.xz" "$dst"
|
|
||||||
mv "$dst.tar.xz" dist/
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- name: Archive (tar)
|
||||||
|
if: '! startsWith(matrix.name, ''windows-'')'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
dst="keyweave-${{ matrix.target }}"
|
||||||
|
tar cavf "$dst.tar.xz" "$dst"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: dist-${{ matrix.target }}
|
name: builds
|
||||||
path: dist
|
retention-days: 1
|
||||||
|
path: |
|
||||||
|
keyweave-*.tar.xz
|
||||||
|
keyweave-x86_64-pc-windows-gnu/keyweave.exe
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: build
|
needs: build
|
||||||
|
@ -152,22 +158,21 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ~/.cargo/bin
|
path: ~/.cargo/bin
|
||||||
key: sign-tools-${{ hashFiles('.github/workflows/release.yml') }}
|
key: sign-tools-${{ hashFiles('.github/workflows/release.yml') }}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
pattern: dist-*
|
name: builds
|
||||||
merge-multiple: true
|
|
||||||
|
|
||||||
- name: Checksums with SHA512 and SHA256
|
- name: Checksums with SHA512 and SHA256
|
||||||
run: |
|
run: |
|
||||||
sha512sum keyweave-* | tee SHA512SUMS
|
sha512sum keyweave-* | tee SHA512SUMS
|
||||||
sha256sum keyweave-* | tee SHA256SUMS
|
sha256sum keyweave-* | tee SHA256SUMS
|
||||||
|
|
||||||
- uses: softprops/action-gh-release@v2
|
- uses: softprops/action-gh-release@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
@ -175,7 +180,7 @@ jobs:
|
||||||
fail_on_unmatched_files: true
|
fail_on_unmatched_files: true
|
||||||
files: |
|
files: |
|
||||||
keyweave-*.tar.xz
|
keyweave-*.tar.xz
|
||||||
keyweave-*.exe
|
keyweave-*/keyweave.exe
|
||||||
*SUMS*
|
*SUMS*
|
||||||
|
|
||||||
- name: Generate SHA256SUM input for Homebrew
|
- name: Generate SHA256SUM input for Homebrew
|
||||||
|
|
104
.github/workflows/tests.yml
vendored
104
.github/workflows/tests.yml
vendored
|
@ -1,104 +0,0 @@
|
||||||
name: Tests
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
id-token: write
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
bicep-pre-check:
|
|
||||||
name: Bicep Pre-check
|
|
||||||
environment: bicep
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
deployed_tag_exists: ${{ steps.check_tag.outputs.DEPLOYED_TAG_EXISTS }}
|
|
||||||
no_changes: ${{ steps.check_changes.outputs.NO_CHANGES }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Fetch complete history
|
|
||||||
run: |
|
|
||||||
git fetch --prune --unshallow --tags
|
|
||||||
- name: Check for deployed tag
|
|
||||||
id: check_tag
|
|
||||||
run: |
|
|
||||||
if git rev-parse --verify deployed >/dev/null 2>&1; then
|
|
||||||
echo "DEPLOYED_TAG_EXISTS=true" >> $GITHUB_OUTPUT
|
|
||||||
echo "LAST_DEPLOYED_COMMIT=$(git rev-list -n 1 deployed)" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "DEPLOYED_TAG_EXISTS=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
- name: Check for changes in bicep folder
|
|
||||||
id: check_changes
|
|
||||||
if: steps.check_tag.outputs.DEPLOYED_TAG_EXISTS == 'true'
|
|
||||||
run: |
|
|
||||||
if git diff --quiet ${{ steps.check_tag.outputs.LAST_DEPLOYED_COMMIT }} HEAD -- bicep/ ; then
|
|
||||||
echo "NO_CHANGES=true" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "NO_CHANGES=false" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
bicep:
|
|
||||||
name: Deploy Azure resources
|
|
||||||
needs: bicep-pre-check
|
|
||||||
if: needs.bicep-pre-check.outputs.deployed_tag_exists == 'false' || needs.bicep-pre-check.outputs.no_changes == 'false'
|
|
||||||
environment: bicep
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: bicep
|
|
||||||
env:
|
|
||||||
LOCATION: eastus
|
|
||||||
DEPLOYMENT_NAME: keyweave-${{ github.run_id }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: azure/login@v2
|
|
||||||
with:
|
|
||||||
client-id: ${{ secrets.AZURE_CLIENT_ID_BICEP }}
|
|
||||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
||||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
|
||||||
|
|
||||||
- name: Deploy Bicep template
|
|
||||||
uses: azure/arm-deploy@v2
|
|
||||||
with:
|
|
||||||
scope: subscription
|
|
||||||
region: ${{ env.LOCATION }}
|
|
||||||
template: bicep/main.bicep
|
|
||||||
deploymentName: ${{ env.DEPLOYMENT_NAME }}
|
|
||||||
- name: Tag Deployment
|
|
||||||
run: |
|
|
||||||
git config --global user.name "github-actions[bot]"
|
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git tag -fa deployed -m "Deployed to Azure"
|
|
||||||
git push origin --tags --force
|
|
||||||
|
|
||||||
tests:
|
|
||||||
name: Run End-to-End Tests
|
|
||||||
needs: bicep
|
|
||||||
if: always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- filter: no_access
|
|
||||||
client-id-ref: AZURE_CLIENT_ID_NO_ACCESS
|
|
||||||
- filter: only_get
|
|
||||||
client-id-ref: AZURE_CLIENT_ID_GET
|
|
||||||
- filter: only_list
|
|
||||||
client-id-ref: AZURE_CLIENT_ID_LIST
|
|
||||||
- filter: get_and_list_access
|
|
||||||
client-id-ref: AZURE_CLIENT_ID_GET_LIST
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: test
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Azure Login
|
|
||||||
uses: azure/login@v2
|
|
||||||
with:
|
|
||||||
client-id: ${{ secrets[matrix.client-id-ref] }}
|
|
||||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
|
||||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
|
||||||
- name: Run ${{ matrix.filter }} tests
|
|
||||||
run: cargo test ${{ matrix.filter }}
|
|
|
@ -1,128 +0,0 @@
|
||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
* Giving and gracefully accepting constructive feedback
|
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
||||||
and learning from the experience
|
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
|
||||||
advances of any kind
|
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or email
|
|
||||||
address, without their explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
[bart@vanderbraak.nl](mailto:bart@vanderbraak.nl).
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.0, available at
|
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
||||||
enforcement ladder](https://github.com/mozilla/diversity).
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
|
||||||
https://www.contributor-covenant.org/translations.
|
|
1362
Cargo.lock
generated
1362
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
21
Cargo.toml
21
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "keyweave"
|
name = "keyweave"
|
||||||
version = "0.3.1"
|
version = "0.2.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Bart van der Braak <bart@vanderbraak.nl>"]
|
authors = ["Bart van der Braak <bart@vanderbraak.nl>"]
|
||||||
keywords = ["azure", "keyvault", "env"]
|
keywords = ["azure", "keyvault", "env"]
|
||||||
|
@ -10,20 +10,13 @@ documentation = "https://docs.rs/keyweave"
|
||||||
repository = "https://github.com/bartvdbraak/keyweave/"
|
repository = "https://github.com/bartvdbraak/keyweave/"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.75"
|
||||||
azure_core = "0.21.0"
|
azure_identity = "0.17.0"
|
||||||
azure_identity = "0.21.0"
|
azure_security_keyvault = "0.17.0"
|
||||||
azure_security_keyvault = "0.21.0"
|
clap = { version = "4.4.8", features = ["derive"] }
|
||||||
clap = { version = "4.5.4", features = ["derive"] }
|
futures = "0.3.29"
|
||||||
futures = "0.3.30"
|
|
||||||
paris = { version = "1.5.15", features = ["macros"] }
|
paris = { version = "1.5.15", features = ["macros"] }
|
||||||
tokio = {version = "1.37.0", features = ["full"]}
|
tokio = {version = "1.34.0", features = ["full"]}
|
||||||
|
|
||||||
[target.'cfg(all(target_os = "linux", any(target_env = "musl", target_arch = "arm", target_arch = "aarch64")))'.dependencies]
|
[target.'cfg(all(target_os = "linux", any(target_env = "musl", target_arch = "arm", target_arch = "aarch64")))'.dependencies]
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
assert_cmd = "2.0.14"
|
|
||||||
assert_fs = "1.1.1"
|
|
||||||
predicates = "3.1.0"
|
|
||||||
serial_test = "3.1.0"
|
|
||||||
|
|
17
README.md
17
README.md
|
@ -3,10 +3,9 @@
|
||||||
[<img alt="github" src="https://img.shields.io/badge/github-bartvdbraak/keyweave-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/bartvdbraak/keyweave)
|
[<img alt="github" src="https://img.shields.io/badge/github-bartvdbraak/keyweave-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/bartvdbraak/keyweave)
|
||||||
[<img alt="crates.io" src="https://img.shields.io/crates/v/keyweave.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/keyweave)
|
[<img alt="crates.io" src="https://img.shields.io/crates/v/keyweave.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/keyweave)
|
||||||
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-keyweave-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/keyweave)
|
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-keyweave-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/keyweave)
|
||||||
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/bartvdbraak/keyweave/checks.yml?style=for-the-badge&branch=main" height="20">](https://github.com/bartvdbraak/keyweave/actions/workflows/checks.yml)
|
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/bartvdbraak/keyweave/checks.yml?style=for-the-badge" height="20">](https://github.com/bartvdbraak/keyweave/actions/workflows/checks.yml)
|
||||||
[<img alt="test status" src="https://img.shields.io/github/actions/workflow/status/bartvdbraak/keyweave/tests.yml?style=for-the-badge&label=tests&branch=main" height="20">](https://github.com/bartvdbraak/keyweave/actions/workflows/tests.yml)
|
|
||||||
|
|
||||||
<img align="right" src="https://github.com/bartvdbraak/keyweave/assets/3996360/5461f53a-5cef-4bde-908a-b8d3bc1c71c5" alt="Keyweave" width="30%">
|
<img align="right" src="https://github.com/bartvdbraak/keyweave/assets/3996360/bed7f004-e897-46e5-98a4-c654251c0e17" alt="Cluster" width="40%">
|
||||||
|
|
||||||
Keyweave is an open-source tool crafted to seamlessly fetch secrets from Azure Key Vault and weave them into a convenient `.env` file. Developed in Rust, Keyweave stands out for its efficiency and user-friendly design, making it an ideal choice for managing your application's secrets.
|
Keyweave is an open-source tool crafted to seamlessly fetch secrets from Azure Key Vault and weave them into a convenient `.env` file. Developed in Rust, Keyweave stands out for its efficiency and user-friendly design, making it an ideal choice for managing your application's secrets.
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ Before diving into Keyweave, ensure you have the following prerequisites:
|
||||||
az login --tenant "your-tenant-guid"
|
az login --tenant "your-tenant-guid"
|
||||||
```
|
```
|
||||||
|
|
||||||
- The identity you logged in with has `Get` and `List` Secret Permissions in the Access Policies of the Key Vault.
|
- Identity has `Get` and `List` Secret Permissions in the Access Policies of the Key Vault.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -69,7 +68,7 @@ cargo build --release
|
||||||
Once built, run Keyweave using Cargo:
|
Once built, run Keyweave using Cargo:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo run -- --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>]
|
cargo run -- --vault_name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
@ -77,17 +76,17 @@ cargo run -- --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>]
|
||||||
With the binary on your `PATH`, run Keyweave as follows:
|
With the binary on your `PATH`, run Keyweave as follows:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
keyweave --vault-name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>]
|
keyweave --vault_name <VAULT_NAME> [--output <FILE>] [--filter <FILTER>]
|
||||||
```
|
```
|
||||||
|
|
||||||
- `--vault-name <VAULT_NAME>`: Sets the name of the Azure Key Vault.
|
- `--vault_name <VAULT_NAME>`: Sets the name of the Azure Key Vault.
|
||||||
- `--output <FILE>`: (Optional) Sets the name of the output file (default: `.env`).
|
- `--output <FILE>`: (Optional) Sets the name of the output file (default: `.env`).
|
||||||
- `--filter <FILTER>`: (Optional) Filters the secrets to be retrieved by name.
|
- `--filter <FILTER>`: (Optional) Filters the secrets to be retrieved by name.
|
||||||
|
|
||||||
### Example
|
## Example
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
keyweave --vault-name my-key-vault --output my-env-file.env --filter my-secret
|
keyweave --vault_name my-key-vault --output my-env-file.env --filter my-secret
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
31
SECURITY.md
31
SECURITY.md
|
@ -1,31 +0,0 @@
|
||||||
# Security Policy
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
|
|
||||||
Use the latest version of Keyweave for the latest security updates.
|
|
||||||
|
|
||||||
## Reporting Vulnerabilities
|
|
||||||
|
|
||||||
To report a security issue, please email [bart@vanderbraak.nl](mailto:bart@vanderbraak.nl) with a detailed description and steps to reproduce. Do not file a public issue for security vulnerabilities.
|
|
||||||
|
|
||||||
### Response Timeline
|
|
||||||
|
|
||||||
We aim to respond to security reports within 48 hours, and to patch the issue within a reasonable timeframe depending on the severity.
|
|
||||||
|
|
||||||
### Responsible Disclosure
|
|
||||||
|
|
||||||
Please allow us a reasonable timeframe to address the issue before publicly disclosing it.
|
|
||||||
|
|
||||||
### Acknowledgements
|
|
||||||
|
|
||||||
We appreciate the responsible disclosure of issues by our users and will acknowledge contributors in our release notes.
|
|
||||||
|
|
||||||
## Security Best Practices
|
|
||||||
|
|
||||||
- Ensure you are running the latest version of Keyweave.
|
|
||||||
- Follow secure password and authentication practices.
|
|
||||||
|
|
||||||
## Contact Alternatives
|
|
||||||
|
|
||||||
If you are unable to send an email, please open an issue on GitHub without disclosing details such that we can establish a alternative form of communication.
|
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
targetScope = 'subscription'
|
|
||||||
|
|
||||||
/*
|
|
||||||
Parameters
|
|
||||||
*/
|
|
||||||
|
|
||||||
@allowed([
|
|
||||||
'D' // Development
|
|
||||||
'T' // Test
|
|
||||||
'A' // Acceptance
|
|
||||||
'P' // Production
|
|
||||||
])
|
|
||||||
param environment string = 'T'
|
|
||||||
param location string = 'westeurope'
|
|
||||||
param name object = {
|
|
||||||
tenantId: 'BVDB'
|
|
||||||
projectId: 'KEYWEAVE'
|
|
||||||
region: 'WEU'
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Variables
|
|
||||||
*/
|
|
||||||
|
|
||||||
var tags = {
|
|
||||||
project: 'keyweave'
|
|
||||||
}
|
|
||||||
var nameFormat = '${name.tenantId}-${name.projectId}-${environment}-${name.region}-{0}-{1:N0}'
|
|
||||||
|
|
||||||
/*
|
|
||||||
Resource Group
|
|
||||||
*/
|
|
||||||
|
|
||||||
resource ResourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' = {
|
|
||||||
name: format(nameFormat, 'RG', 1)
|
|
||||||
location: location
|
|
||||||
tags: tags
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Module for Log Analytics Workspace
|
|
||||||
*/
|
|
||||||
|
|
||||||
module LogAnalyticsWorkspace 'modules/law.bicep' = {
|
|
||||||
name: 'LogAnalyticsWorkspace'
|
|
||||||
scope: ResourceGroup
|
|
||||||
params: {
|
|
||||||
nameFormat: nameFormat
|
|
||||||
location: location
|
|
||||||
tags: tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Module for Managed Identities
|
|
||||||
*/
|
|
||||||
|
|
||||||
module ManagedIdentities 'modules/id.bicep' = {
|
|
||||||
name: 'ManagedIdentities'
|
|
||||||
scope: ResourceGroup
|
|
||||||
params: {
|
|
||||||
nameFormat: nameFormat
|
|
||||||
location: location
|
|
||||||
tags: tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Module for KeyVault
|
|
||||||
*/
|
|
||||||
|
|
||||||
module KeyVault 'modules/kv.bicep' = {
|
|
||||||
name: 'KeyVault'
|
|
||||||
scope: ResourceGroup
|
|
||||||
dependsOn: [
|
|
||||||
LogAnalyticsWorkspace
|
|
||||||
]
|
|
||||||
params: {
|
|
||||||
nameFormat: nameFormat
|
|
||||||
location: location
|
|
||||||
tags: tags
|
|
||||||
|
|
||||||
identities: ManagedIdentities.outputs.identities
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
param nameFormat string
|
|
||||||
param location string
|
|
||||||
param tags object
|
|
||||||
|
|
||||||
param identityEnvironments array = [
|
|
||||||
'none'
|
|
||||||
'get'
|
|
||||||
'list'
|
|
||||||
'getlist'
|
|
||||||
]
|
|
||||||
|
|
||||||
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = [for (environment, index) in identityEnvironments: {
|
|
||||||
name: format(nameFormat, 'ID', index+1)
|
|
||||||
location: location
|
|
||||||
tags: tags
|
|
||||||
}]
|
|
||||||
|
|
||||||
resource federatedCredential 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = [for (environment, index) in identityEnvironments: {
|
|
||||||
name: environment
|
|
||||||
parent: managedIdentity[index]
|
|
||||||
properties: {
|
|
||||||
issuer: 'https://token.actions.githubusercontent.com'
|
|
||||||
subject: 'repo:bartvdbraak/keyweave:environment:test'
|
|
||||||
audiences: [
|
|
||||||
'api://AzureADTokenExchange'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
output identities array = [for (environment, index) in identityEnvironments: {
|
|
||||||
name: environment
|
|
||||||
id: managedIdentity[index].properties.principalId
|
|
||||||
}]
|
|
|
@ -1,121 +0,0 @@
|
||||||
param nameFormat string
|
|
||||||
param location string
|
|
||||||
param tags object
|
|
||||||
|
|
||||||
param identities array
|
|
||||||
|
|
||||||
var accessPolicies = [for identity in identities: {
|
|
||||||
tenantId: tenant().tenantId
|
|
||||||
objectId: identity.id
|
|
||||||
permissions: {
|
|
||||||
secrets: contains(identity.name, 'get') && contains(identity.name, 'list') ? ['Get', 'List'] : contains(identity.name, 'get') ? ['Get'] : contains(identity.name, 'list') ? ['List'] : []
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
/*
|
|
||||||
Log Analytics Workspace (existing)
|
|
||||||
*/
|
|
||||||
|
|
||||||
resource _logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' existing = {
|
|
||||||
name: format(nameFormat, 'LAW', 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Key Vault
|
|
||||||
*/
|
|
||||||
|
|
||||||
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
|
|
||||||
name: replace(toLower(format(nameFormat, 'KVT', 1)), '-', '')
|
|
||||||
location: location
|
|
||||||
tags: tags
|
|
||||||
properties: {
|
|
||||||
sku: {
|
|
||||||
family: 'A'
|
|
||||||
name: 'standard'
|
|
||||||
}
|
|
||||||
tenantId: tenant().tenantId
|
|
||||||
enableSoftDelete: true
|
|
||||||
enablePurgeProtection: true
|
|
||||||
accessPolicies: accessPolicies
|
|
||||||
}
|
|
||||||
resource testSecret 'secrets' = {
|
|
||||||
name: 'testSecret'
|
|
||||||
properties: {
|
|
||||||
value: 'testSecretValue'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resource filterTestSecret 'secrets' = {
|
|
||||||
name: 'filterTestSecret'
|
|
||||||
properties: {
|
|
||||||
value: 'filterTestSecretValue'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Key Vault
|
|
||||||
*/
|
|
||||||
|
|
||||||
resource keyVaultWithFirewall 'Microsoft.KeyVault/vaults@2023-07-01' = {
|
|
||||||
name: replace(toLower(format(nameFormat, 'KVT', 2)), '-', '')
|
|
||||||
location: location
|
|
||||||
tags: tags
|
|
||||||
properties: {
|
|
||||||
sku: {
|
|
||||||
family: 'A'
|
|
||||||
name: 'standard'
|
|
||||||
}
|
|
||||||
tenantId: tenant().tenantId
|
|
||||||
enableSoftDelete: true
|
|
||||||
enablePurgeProtection: true
|
|
||||||
accessPolicies: accessPolicies
|
|
||||||
networkAcls: {
|
|
||||||
defaultAction: 'Deny'
|
|
||||||
ipRules: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resource testSecret 'secrets' = {
|
|
||||||
name: 'testSecret'
|
|
||||||
properties: {
|
|
||||||
value: 'testSecretValue'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resource filterTestSecret 'secrets' = {
|
|
||||||
name: 'filterTestSecret'
|
|
||||||
properties: {
|
|
||||||
value: 'filterTestSecretValue'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Diagnostic Settings for Key Vaults
|
|
||||||
*/
|
|
||||||
|
|
||||||
resource keyVaultDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
|
|
||||||
name: 'keyVaultLogging'
|
|
||||||
scope: keyVault
|
|
||||||
properties: {
|
|
||||||
workspaceId: _logAnalyticsWorkspace.id
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
category: 'AuditEvent'
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource keyVaultWithFirewallDiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
|
|
||||||
name: 'keyVaultLogging'
|
|
||||||
scope: keyVaultWithFirewall
|
|
||||||
properties: {
|
|
||||||
workspaceId: _logAnalyticsWorkspace.id
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
category: 'AuditEvent'
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
param nameFormat string
|
|
||||||
param location string
|
|
||||||
param tags object
|
|
||||||
|
|
||||||
/*
|
|
||||||
Log Analytics Workspace
|
|
||||||
*/
|
|
||||||
|
|
||||||
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
|
|
||||||
name: format(nameFormat, 'LAW', 1)
|
|
||||||
location: location
|
|
||||||
tags: tags
|
|
||||||
properties: {
|
|
||||||
sku: {
|
|
||||||
name: 'PerGB2018'
|
|
||||||
}
|
|
||||||
features: {
|
|
||||||
enableLogAccessUsingOnlyResourcePermissions: true
|
|
||||||
}
|
|
||||||
workspaceCapping: {
|
|
||||||
dailyQuotaGb: json('0.025')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
97
src/main.rs
97
src/main.rs
|
@ -1,31 +1,16 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use azure_identity::{DefaultAzureCredential, TokenCredentialOptions};
|
use azure_identity::DefaultAzureCredential;
|
||||||
use azure_security_keyvault::prelude::KeyVaultGetSecretsResponse;
|
use azure_security_keyvault::prelude::KeyVaultGetSecretsResponse;
|
||||||
use azure_security_keyvault::KeyvaultClient;
|
use azure_security_keyvault::KeyvaultClient;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use paris::{error, Logger};
|
use paris::{error, Logger};
|
||||||
use std::error::Error;
|
|
||||||
use std::fmt;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct CustomError {
|
|
||||||
message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for CustomError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for CustomError {}
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct Opts {
|
struct Opts {
|
||||||
|
@ -42,26 +27,6 @@ struct Opts {
|
||||||
filter: Option<String>,
|
filter: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_vault_dns(vault_name: &str) -> Result<()> {
|
|
||||||
let vault_host = format!("{}.vault.azure.net", vault_name);
|
|
||||||
|
|
||||||
let lookup_result = { tokio::net::lookup_host((vault_host.as_str(), 443)).await };
|
|
||||||
|
|
||||||
match lookup_result {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(_err) => {
|
|
||||||
error!("DNS lookup failed for Key Vault: {}", vault_name);
|
|
||||||
error!(
|
|
||||||
"Please check that the Key Vault exists or that you have no connectivity issues."
|
|
||||||
);
|
|
||||||
Err(CustomError {
|
|
||||||
message: "An error occurred while fetching secrets".to_string(),
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_secrets_from_key_vault(
|
async fn fetch_secrets_from_key_vault(
|
||||||
client: &KeyvaultClient,
|
client: &KeyvaultClient,
|
||||||
filter: Option<&str>,
|
filter: Option<&str>,
|
||||||
|
@ -73,33 +38,8 @@ async fn fetch_secrets_from_key_vault(
|
||||||
let page = match page {
|
let page = match page {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
Logger::new().newline(1);
|
error!("Failed to fetch secrets page: {}", err);
|
||||||
match err.as_http_error() {
|
return Err(err.into()); // Convert the error into anyhow::Error
|
||||||
Some(err) => {
|
|
||||||
if err
|
|
||||||
.error_message()
|
|
||||||
.unwrap()
|
|
||||||
.contains("does not have secrets list permission on key vault")
|
|
||||||
{
|
|
||||||
error!("Make sure you have List permissions on the Key Vault.")
|
|
||||||
} else if err
|
|
||||||
.error_message()
|
|
||||||
.unwrap()
|
|
||||||
.contains("is not authorized and caller is not a trusted service")
|
|
||||||
{
|
|
||||||
error!("Make sure you're on the Key Vaults Firewall allowlist.")
|
|
||||||
} else {
|
|
||||||
error!("HTTP Error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!("Error: {}", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return Err(CustomError {
|
|
||||||
message: "An error occurred while fetching secrets".to_string(),
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
secret_values
|
secret_values
|
||||||
|
@ -165,8 +105,8 @@ async fn fetch_and_send_secret(
|
||||||
let _ = tx.send((secret_id.clone(), bundle.value.clone())).await;
|
let _ = tx.send((secret_id.clone(), bundle.value.clone())).await;
|
||||||
(secret_id, bundle.value)
|
(secret_id, bundle.value)
|
||||||
}
|
}
|
||||||
Err(_err) => {
|
Err(err) => {
|
||||||
error!("Error fetching secret. Make sure you have Get permissions on the Key Vault.");
|
error!("Error fetching secret: {}", err);
|
||||||
(secret_id, String::new())
|
(secret_id, String::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,21 +117,15 @@ fn create_env_file(secrets: Vec<(String, String)>, output_file: &str) -> Result<
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Failed to create output file: {}", err);
|
error!("Failed to create output file: {}", err);
|
||||||
return Err(CustomError {
|
return Err(err.into());
|
||||||
message: "n Aerror occurred creating file".to_string(),
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (key, value) in secrets {
|
for (key, value) in secrets {
|
||||||
if let Some(secret_name) = key.split('/').last() {
|
if let Some(secret_name) = key.split('/').last() {
|
||||||
if let Err(_err) = writeln!(file, "{}={}", secret_name, value) {
|
if let Err(err) = writeln!(file, "{}={}", secret_name, value) {
|
||||||
error!("Failed to write to output file: {}", output_file);
|
error!("Failed to write to output file: {}: {}", output_file, err);
|
||||||
return Err(CustomError {
|
return Err(err.into());
|
||||||
message: "An error occurred while writing secrets to file".to_string(),
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,7 +158,7 @@ mod tests {
|
||||||
vec!["SECRET_KEY=secret_value1", "API_KEY=secret_value2",]
|
vec!["SECRET_KEY=secret_value1", "API_KEY=secret_value2",]
|
||||||
);
|
);
|
||||||
|
|
||||||
fs::remove_file(test_file)?;
|
fs::remove_file(test_file)?; // Clean up the test file
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,17 +166,12 @@ mod tests {
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let opts: Opts = Opts::parse();
|
let opts: Opts = Opts::parse();
|
||||||
let mut log: Logger<'_> = Logger::new();
|
let mut log = Logger::new();
|
||||||
|
|
||||||
let vault_url = format!("https://{}.vault.azure.net", opts.vault_name);
|
let vault_url = format!("https://{}.vault.azure.net", opts.vault_name);
|
||||||
|
|
||||||
log.loading("Detecting credentials.");
|
log.loading("Detecting credentials.");
|
||||||
let credential_options = TokenCredentialOptions::default();
|
let credential = DefaultAzureCredential::default();
|
||||||
let credential =
|
|
||||||
DefaultAzureCredential::create(credential_options).map_err(|e| CustomError {
|
|
||||||
message: format!("Failed to create DefaultAzureCredential: {}", e),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let client = match KeyvaultClient::new(&vault_url, std::sync::Arc::new(credential)) {
|
let client = match KeyvaultClient::new(&vault_url, std::sync::Arc::new(credential)) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -252,8 +181,6 @@ async fn main() -> Result<()> {
|
||||||
};
|
};
|
||||||
log.success("Detected credentials.");
|
log.success("Detected credentials.");
|
||||||
|
|
||||||
check_vault_dns(&opts.vault_name).await?;
|
|
||||||
|
|
||||||
log.loading(format!(
|
log.loading(format!(
|
||||||
"Fetching secrets from Key Vault: <blue>{}</>",
|
"Fetching secrets from Key Vault: <blue>{}</>",
|
||||||
opts.vault_name
|
opts.vault_name
|
||||||
|
|
151
tests/e2e.rs
151
tests/e2e.rs
|
@ -1,151 +0,0 @@
|
||||||
use assert_cmd::prelude::*;
|
|
||||||
use assert_fs::prelude::*;
|
|
||||||
use assert_fs::TempDir;
|
|
||||||
use predicates::prelude::*;
|
|
||||||
use serial_test::serial;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
static BINARY: &str = "keyweave";
|
|
||||||
static KEYVAULT: &str = "bvdbkeyweavetweukvt1";
|
|
||||||
static FIREWALL_KEYVAULT: &str = "bvdbkeyweavetweukvt2";
|
|
||||||
static NON_EXISTENT_KEYVAULT: &str = "bvdbkeyweavetweukvt3";
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn test_no_access_policies() {
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
|
||||||
let output_path = temp_dir.child(".env");
|
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin(BINARY).unwrap();
|
|
||||||
cmd.arg("--vault-name")
|
|
||||||
.arg(KEYVAULT)
|
|
||||||
.arg("--output")
|
|
||||||
.arg(output_path.path());
|
|
||||||
cmd.assert().failure().stderr(predicate::str::contains(
|
|
||||||
"Make sure you have List permissions on the Key Vault.",
|
|
||||||
));
|
|
||||||
|
|
||||||
temp_dir.close().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn test_only_get_access_policy() {
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
|
||||||
let output_path = temp_dir.child(".env");
|
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin(BINARY).unwrap();
|
|
||||||
cmd.arg("--vault-name")
|
|
||||||
.arg(KEYVAULT)
|
|
||||||
.arg("--output")
|
|
||||||
.arg(output_path.path());
|
|
||||||
cmd.assert().failure().stderr(predicate::str::contains(
|
|
||||||
"Make sure you have List permissions on the Key Vault.",
|
|
||||||
));
|
|
||||||
|
|
||||||
temp_dir.close().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test with only List access policy - expected to succeed with get errors.
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn test_only_list_access_policy() {
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
|
||||||
let output_path = temp_dir.child(".env");
|
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin(BINARY).unwrap();
|
|
||||||
cmd.arg("--vault-name")
|
|
||||||
.arg(KEYVAULT)
|
|
||||||
.arg("--output")
|
|
||||||
.arg(output_path.path());
|
|
||||||
cmd.assert().success().stderr(predicate::str::contains(
|
|
||||||
"Make sure you have Get permissions on the Key Vault.",
|
|
||||||
));
|
|
||||||
|
|
||||||
temp_dir.close().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test with both Get and List access policies - expected to pass.
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn test_get_and_list_access_policies() {
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
|
||||||
let output_path = temp_dir.child(".env");
|
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin(BINARY).unwrap();
|
|
||||||
cmd.arg("--vault-name")
|
|
||||||
.arg(KEYVAULT)
|
|
||||||
.arg("--output")
|
|
||||||
.arg(output_path.path());
|
|
||||||
cmd.assert().success();
|
|
||||||
|
|
||||||
output_path.assert(predicate::path::is_file());
|
|
||||||
output_path.assert(predicate::str::contains("testSecret=testSecretValue"));
|
|
||||||
output_path.assert(predicate::str::contains(
|
|
||||||
"filterTestSecret=filterTestSecretValue",
|
|
||||||
));
|
|
||||||
|
|
||||||
temp_dir.close().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test with both Get and List access policies and filter - expected to pass.
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn test_get_and_list_access_policies_filter() {
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
|
||||||
let output_path = temp_dir.child(".env");
|
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin(BINARY).unwrap();
|
|
||||||
cmd.arg("--vault-name")
|
|
||||||
.arg(KEYVAULT)
|
|
||||||
.arg("--output")
|
|
||||||
.arg(output_path.path())
|
|
||||||
.arg("--filter")
|
|
||||||
.arg("filter");
|
|
||||||
cmd.assert().success();
|
|
||||||
|
|
||||||
output_path.assert(predicate::path::is_file());
|
|
||||||
output_path.assert(predicate::str::contains(
|
|
||||||
"filterTestSecret=filterTestSecretValue",
|
|
||||||
));
|
|
||||||
|
|
||||||
temp_dir.close().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test with both Get and List access policies on a Key Vault with Firewall - expected to fail.
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn test_get_and_list_access_policies_firewall() {
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
|
||||||
let output_path = temp_dir.child(".env");
|
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin(BINARY).unwrap();
|
|
||||||
cmd.arg("--vault-name")
|
|
||||||
.arg(FIREWALL_KEYVAULT)
|
|
||||||
.arg("--output")
|
|
||||||
.arg(output_path.path());
|
|
||||||
cmd.assert().failure().stderr(predicate::str::contains(
|
|
||||||
"Make sure you're on the Key Vaults Firewall allowlist.",
|
|
||||||
));
|
|
||||||
|
|
||||||
temp_dir.close().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test with both Get and List access policies on a non-existent Key Vault - expected to fail.
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
|
||||||
async fn test_get_and_list_access_policies_non_existent() {
|
|
||||||
let temp_dir = TempDir::new().unwrap();
|
|
||||||
let output_path = temp_dir.child(".env");
|
|
||||||
|
|
||||||
let mut cmd = Command::cargo_bin(BINARY).unwrap();
|
|
||||||
cmd.arg("--vault-name")
|
|
||||||
.arg(NON_EXISTENT_KEYVAULT)
|
|
||||||
.arg("--output")
|
|
||||||
.arg(output_path.path());
|
|
||||||
cmd.assert().failure().stderr(predicate::str::contains(
|
|
||||||
"Please check that the Key Vault exists or that you have no connectivity issues.",
|
|
||||||
));
|
|
||||||
|
|
||||||
temp_dir.close().unwrap();
|
|
||||||
}
|
|
Loading…
Reference in a new issue