Merge pull request #33 from bartvdbraak/feat/31-e2e-tests-and-workflows

End-to-end test infrastructure and workflow
This commit is contained in:
Bart van der Braak 2023-11-30 23:59:52 +01:00 committed by GitHub
commit bbeb3a3fe3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 864 additions and 8 deletions

View file

@ -39,8 +39,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Run tests
run: cargo test --all
- name: Run unit tests
run: cargo test --bins
build:
runs-on: ubuntu-latest
steps:

101
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,101 @@
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
concurrency:
group: bicep
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 }}
tests-no-access:
name: Tests with No Access
needs: bicep
runs-on: ubuntu-latest
environment: test
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_NO_ACCESS }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Run all tests
run: cargo test no_access
tests-get:
name: Tests with Get
needs: bicep
runs-on: ubuntu-latest
environment: test
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_GET }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Run all tests
run: cargo test only_get
tests-list:
name: Tests with List
needs: bicep
runs-on: ubuntu-latest
environment: test
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_LIST }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Run all tests
run: cargo test only_list
tests-get-list:
name: Tests with Get and List
needs: bicep
runs-on: ubuntu-latest
environment: test
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: 'Az CLI login'
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID_GET_LIST }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Run all tests
run: cargo test get_and_list_access

297
Cargo.lock generated
View file

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

View file

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

85
bicep/main.bicep Normal file
View file

@ -0,0 +1,85 @@
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@2022-09-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
}
}

33
bicep/modules/id.bicep Normal file
View file

@ -0,0 +1,33 @@
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
}]

121
bicep/modules/kv.bicep Normal file
View file

@ -0,0 +1,121 @@
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@2022-10-01' existing = {
name: format(nameFormat, 'LAW', 1)
}
/*
Key Vault
*/
resource keyVault 'Microsoft.KeyVault/vaults@2023-02-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-02-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
}
]
}
}

24
bicep/modules/law.bicep Normal file
View file

@ -0,0 +1,24 @@
param nameFormat string
param location string
param tags object
/*
Log Analytics Workspace
*/
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: format(nameFormat, 'LAW', 1)
location: location
tags: tags
properties: {
sku: {
name: 'PerGB2018'
}
features: {
enableLogAccessUsingOnlyResourcePermissions: true
}
workspaceCapping: {
dailyQuotaGb: json('0.025')
}
}
}

View file

@ -1,4 +1,5 @@
use anyhow::Result;
use azure_core::error::HttpError;
use azure_identity::DefaultAzureCredential;
use azure_security_keyvault::prelude::KeyVaultGetSecretsResponse;
use azure_security_keyvault::KeyvaultClient;
@ -27,6 +28,23 @@ struct Opts {
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(err.into())
}
}
}
async fn fetch_secrets_from_key_vault(
client: &KeyvaultClient,
filter: Option<&str>,
@ -38,8 +56,27 @@ async fn fetch_secrets_from_key_vault(
let page = match page {
Ok(p) => p,
Err(err) => {
error!("Failed to fetch secrets page: {}", err);
return Err(err.into()); // Convert the error into anyhow::Error
error!("\n");
error!("Failed to fetch secrets.");
let specific_error = err.downcast_ref::<HttpError>();
if let Some(specific_error) = specific_error {
if specific_error
.error_message()
.unwrap()
.to_string()
.contains("does not have secrets list permission on key vault")
{
error!("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")
{
error!("Make sure you're on the Key Vaults Firewall allowlist.");
}
}
return Err(err.into());
}
};
secret_values
@ -105,8 +142,8 @@ async fn fetch_and_send_secret(
let _ = tx.send((secret_id.clone(), bundle.value.clone())).await;
(secret_id, bundle.value)
}
Err(err) => {
error!("Error fetching secret: {}", err);
Err(_err) => {
error!("Error fetching secret. Make sure you have Get permissions on the Key Vault.");
(secret_id, String::new())
}
}
@ -158,7 +195,7 @@ mod tests {
vec!["SECRET_KEY=secret_value1", "API_KEY=secret_value2",]
);
fs::remove_file(test_file)?; // Clean up the test file
fs::remove_file(test_file)?;
Ok(())
}
}
@ -181,6 +218,8 @@ async fn main() -> Result<()> {
};
log.success("Detected credentials.");
check_vault_dns(&opts.vault_name).await?;
log.loading(format!(
"Fetching secrets from Key Vault: <blue>{}</>",
opts.vault_name

151
tests/e2e.rs Normal file
View file

@ -0,0 +1,151 @@
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();
}