From 38a15a3bcd00ea3d6ca19251193a51a0a8ff911f Mon Sep 17 00:00:00 2001 From: Bart van der Braak Date: Wed, 22 Nov 2023 05:17:04 +0100 Subject: [PATCH] feat: e2e tests within rust instead --- .github/workflows/checks.yml | 2 +- .github/workflows/e2e.yml | 153 ------------------ .github/workflows/tests.yml | 45 ++++++ Cargo.lock | 297 ++++++++++++++++++++++++++++++++++- Cargo.toml | 7 + bicep/modules/id.bicep | 2 +- src/main.rs | 12 ++ tests/e2e.rs | 188 ++++++++++++++++++++++ 8 files changed, 550 insertions(+), 156 deletions(-) delete mode 100644 .github/workflows/e2e.yml create mode 100644 .github/workflows/tests.yml create mode 100644 tests/e2e.rs diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index df67ff3..cf465d3 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -40,7 +40,7 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - name: Run tests - run: cargo test --all + run: cargo test --bins build: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index 762b80c..0000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,153 +0,0 @@ -name: Checks - -permissions: - id-token: write - contents: read - -env: - VAULT_NAME: bvdbkeyweavetweukvt{0} - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - name: Build Keyweave - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - name: Build project - run: cargo build --all --release - - uses: actions/upload-artifact@v3.1.3 - with: - path: target/release/keyweave - - bicep: - name: Deploy Azure resources - environment: bicep - runs-on: ubuntu-latest - env: - LOCATION: eastus - DEPLOYMENT_NAME: keyweave-${{ github.run_id }} - steps: - - uses: actions/checkout@v3 - - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Deploy Bicep template - uses: azure/arm-deploy@v1 - with: - scope: subscription - region: ${{ env.LOCATION }} - template: bicep/main.bicep - deploymentName: ${{ env.DEPLOYMENT_NAME }} - - none-test: - name: Tests without access - needs: [build, bicep] - runs-on: ubuntu-latest - environment: none - steps: - - uses: actions/download-artifact@v3.0.2 - - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Use Keyweave with No Access Policies - run: | - chmod +x ./artifact/keyweave - ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} - - get-test: - name: Tests with Get access - needs: [build, bicep] - runs-on: ubuntu-latest - environment: get - steps: - - uses: actions/download-artifact@v3.0.2 - - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Use Keyweave with Only Get Access Policy - run: | - chmod +x ./artifact/keyweave - ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} - - list-test: - name: Tests with List access - needs: [build, bicep] - runs-on: ubuntu-latest - environment: list - steps: - - uses: actions/download-artifact@v3.0.2 - - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Use Keyweave with Only List Access Policy - run: | - chmod +x ./artifact/keyweave - ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} - - get-list-test: - name: Tests with Get and List access - needs: [build, bicep] - runs-on: ubuntu-latest - environment: getlist - steps: - - uses: actions/download-artifact@v3.0.2 - - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Use Keyweave with both Get and List Access Policies - run: | - chmod +x ./artifact/keyweave - ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} - - - name: Use Keyweave with a filter - run: | - ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} --filter "filter" - - - name: Use Keyweave with a complex file path - run: | - mkdir -p "user/projects/project 1/src/lib" - ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} --output "user/projects/project 1/src/lib/.env" - - - name: Use Keyweave with a non-existent Key Vault - run: ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }}1234 - - - name: Use Keyweave with a firewalled Key Vault - run: ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '2') }} - - - name: Use Keyweave with a no permissions - run: | - mkdir -p "user/projects/project 1/src/lib" - ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} --output "/.env" - - - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.OTHER_SUBSCRIPTION_ID }} - - name: Use Keyweave while logged into other Subscription - run: ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} - - # - uses: azure/login@v1 - # with: - # client-id: ${{ secrets.AZURE_CLIENT_ID }} - # tenant-id: ${{ secrets.OTHER_TENANT_ID }} - # subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - # - name: Use Keyweave while logged into other Azure Tenant - # run: ./artifact/keyweave --vault-name ${{ format(env.VAULT_NAME, '1') }} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..f2ce024 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,45 @@ +name: Tests + +permissions: + id-token: write + contents: read + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + bicep: + name: Deploy Azure resources + environment: bicep + runs-on: ubuntu-latest + env: + LOCATION: eastus + DEPLOYMENT_NAME: keyweave-${{ github.run_id }} + steps: + - uses: actions/checkout@v3 + - uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + - name: Deploy Bicep template + uses: azure/arm-deploy@v1 + with: + scope: subscription + region: ${{ env.LOCATION }} + template: bicep/main.bicep + deploymentName: ${{ env.DEPLOYMENT_NAME }} + + test: + name: Tests + needs: bicep + runs-on: ubuntu-latest + environment: test + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Run all tests + run: cargo test --all diff --git a/Cargo.lock b/Cargo.lock index d58f8b6..26084b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -86,6 +95,36 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "assert_cmd" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "assert_fs" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f070617a68e5c2ed5d06ee8dd620ee18fb72b99f6c094bed34cf8ab07c875b48" +dependencies = [ + "anstyle", + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -235,6 +274,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -382,6 +432,19 @@ dependencies = [ "typenum", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.2", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "deranged" version = "0.3.9" @@ -392,6 +455,12 @@ dependencies = [ "serde", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -402,12 +471,24 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dyn-clone" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -469,6 +550,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -643,6 +733,30 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "globset" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "h2" version = "0.3.21" @@ -668,6 +782,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + [[package]] name = "heck" version = "0.4.1" @@ -804,6 +924,23 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -811,7 +948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -835,6 +972,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -855,12 +1001,17 @@ name = "keyweave" version = "0.2.3" dependencies = [ "anyhow", + "assert_cmd", + "assert_fs", + "azure_core", "azure_identity", "azure_security_keyvault", "clap", "futures", "openssl", "paris", + "predicates", + "serial_test", "tokio", ] @@ -948,6 +1099,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-traits" version = "0.2.17" @@ -1161,6 +1318,37 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -1259,6 +1447,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "reqwest" version = "0.11.22" @@ -1333,6 +1550,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.22" @@ -1441,6 +1667,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +dependencies = [ + "dashmap", + "futures", + "lazy_static", + "log", + "parking_lot", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1547,6 +1798,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" version = "1.0.50" @@ -1567,6 +1824,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.30" @@ -1774,12 +2041,31 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "waker-fn" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1906,6 +2192,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 57e9320..b1ddc77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/bartvdbraak/keyweave/" [dependencies] anyhow = "1.0.75" +azure_core = "0.17.0" azure_identity = "0.17.0" azure_security_keyvault = "0.17.0" clap = { version = "4.4.8", features = ["derive"] } @@ -20,3 +21,9 @@ tokio = {version = "1.34.0", features = ["full"]} [target.'cfg(all(target_os = "linux", any(target_env = "musl", target_arch = "arm", target_arch = "aarch64")))'.dependencies] openssl = { version = "0.10", features = ["vendored"] } + +[dev-dependencies] +assert_cmd = "2.0.12" +assert_fs = "1.0.13" +predicates = "3.0.4" +serial_test = "2.0.0" diff --git a/bicep/modules/id.bicep b/bicep/modules/id.bicep index cfc65fa..3f95431 100644 --- a/bicep/modules/id.bicep +++ b/bicep/modules/id.bicep @@ -20,7 +20,7 @@ resource federatedCredential 'Microsoft.ManagedIdentity/userAssignedIdentities/f parent: managedIdentity[index] properties: { issuer: 'https://token.actions.githubusercontent.com' - subject: 'repo:bartvdbraak/keyweave:environment:${environment}' + subject: 'repo:bartvdbraak/keyweave:environment:test' audiences: [ 'api://AzureADTokenExchange' ] diff --git a/src/main.rs b/src/main.rs index 6aea81a..2b11a37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ use std::io::Write; use std::sync::Arc; use tokio::sync::mpsc; use tokio::sync::Semaphore; +use azure_core::error::HttpError; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] @@ -59,6 +60,15 @@ async fn fetch_secrets_from_key_vault( Err(err) => { log!("\n"); error!("Failed to fetch secrets page: {}", err); + let specific_error = err.downcast_ref::(); + if let Some(specific_error) = specific_error { + // Check the contents of the specific error + if specific_error.error_message().unwrap().to_string().contains("does not have secrets list permission on key vault") { + info!("Make sure you have List permissions on the Key Vault."); + } else if specific_error.error_message().unwrap().to_string().contains("is not authorized and caller is not a trusted service") { + info!("Make sure you're on the Key Vaults Firewall allowlist."); + } + } return Err(err.into()); } }; @@ -69,6 +79,7 @@ async fn fetch_secrets_from_key_vault( Ok(secret_values) } + async fn fetch_secrets_from_page( client: &azure_security_keyvault::SecretClient, page: &KeyVaultGetSecretsResponse, @@ -127,6 +138,7 @@ async fn fetch_and_send_secret( } Err(err) => { error!("Error fetching secret: {}", err); + info!("Make sure you have Get permissions on the Key Vault."); (secret_id, String::new()) } } diff --git a/tests/e2e.rs b/tests/e2e.rs new file mode 100644 index 0000000..58b256b --- /dev/null +++ b/tests/e2e.rs @@ -0,0 +1,188 @@ +use assert_cmd::prelude::*; +use assert_fs::prelude::*; +use assert_fs::TempDir; +use std::process::Command; +use predicates::prelude::*; +use serial_test::serial; +use std::env; + +static BINARY: &str = "keyweave"; +static KEYVAULT: &str = "bvdbkeyweavetweukvt1"; +static FIREWALL_KEYVAULT: &str = "bvdbkeyweavetweukvt2"; +static NON_EXISTENT_KEYVAULT: &str = "bvdbkeyweavetweukvt3"; + +fn azure_cli_login(client_id: String, tenant_id: String, subscription_id: String) -> Result<(), std::io::Error> { + Command::new("az") + .arg("login") + .arg("--identity") + .arg("--username") + .arg(client_id) + .arg("--tenant") + .arg(tenant_id) + .output()?; + + Command::new("az") + .arg("account") + .arg("set") + .arg("--subscription") + .arg(subscription_id) + .output()?; + + Ok(()) +} + +/// Test with no access policies - expected to fail. +#[tokio::test] +#[serial] +async fn test_no_access_policies() { + azure_cli_login( + env::var("AZURE_CLIENT_ID_NO_ACCESS").expect("Failed to get AZURE_CLIENT_ID_NO_ACCESS"), + env::var("AZURE_TENANT_ID").expect("Failed to get AZURE_TENANT_ID"), + env::var("AZURE_SUBSCRIPTION_ID").expect("Failed to get AZURE_SUBSCRIPTION_ID"), + ).expect("Failed to log in to Azure CLI"); + + 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 Get access policy - expected to fail. +#[tokio::test] +#[serial] +async fn test_only_get_access_policy() { + azure_cli_login( + env::var("AZURE_CLIENT_ID_GET").expect("Failed to get AZURE_CLIENT_ID_GET"), + env::var("AZURE_TENANT_ID").expect("Failed to get AZURE_TENANT_ID"), + env::var("AZURE_SUBSCRIPTION_ID").expect("Failed to get AZURE_SUBSCRIPTION_ID"), + ).expect("Failed to log in to Azure CLI"); + + 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() { + azure_cli_login( + env::var("AZURE_CLIENT_ID_LIST").expect("Failed to get AZURE_CLIENT_ID_LIST"), + env::var("AZURE_TENANT_ID").expect("Failed to get AZURE_TENANT_ID"), + env::var("AZURE_SUBSCRIPTION_ID").expect("Failed to get AZURE_SUBSCRIPTION_ID"), + ).expect("Failed to log in to Azure CLI"); + + 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() { + azure_cli_login( + env::var("AZURE_CLIENT_ID_GET_LIST").expect("Failed to get AZURE_CLIENT_ID_GET_LIST"), + env::var("AZURE_TENANT_ID").expect("Failed to get AZURE_TENANT_ID"), + env::var("AZURE_SUBSCRIPTION_ID").expect("Failed to get AZURE_SUBSCRIPTION_ID"), + ).expect("Failed to log in to Azure CLI"); + + 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() { + azure_cli_login( + env::var("AZURE_CLIENT_ID_GET_LIST").expect("Failed to get AZURE_CLIENT_ID_GET_LIST"), + env::var("AZURE_TENANT_ID").expect("Failed to get AZURE_TENANT_ID"), + env::var("AZURE_SUBSCRIPTION_ID").expect("Failed to get AZURE_SUBSCRIPTION_ID"), + ).expect("Failed to log in to Azure CLI"); + + 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() { + azure_cli_login( + env::var("AZURE_CLIENT_ID_GET_LIST").expect("Failed to get AZURE_CLIENT_ID_GET_LIST"), + env::var("AZURE_TENANT_ID").expect("Failed to get AZURE_TENANT_ID"), + env::var("AZURE_SUBSCRIPTION_ID").expect("Failed to get AZURE_SUBSCRIPTION_ID"), + ).expect("Failed to log in to Azure CLI"); + + 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() { + azure_cli_login( + env::var("AZURE_CLIENT_ID_GET_LIST").expect("Failed to get AZURE_CLIENT_ID_GET_LIST"), + env::var("AZURE_TENANT_ID").expect("Failed to get AZURE_TENANT_ID"), + env::var("AZURE_SUBSCRIPTION_ID").expect("Failed to get AZURE_SUBSCRIPTION_ID"), + ).expect("Failed to log in to Azure CLI"); + + 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(); +} +