From f8665efe22b9e8f18777a8459183c5c9afa03ad1 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Wed, 11 Jun 2025 12:03:18 +0100 Subject: [PATCH 1/2] feat: Add KeyVault framework support --- ql/lib/codeql/bicep/Frameworks.qll | 3 +- .../bicep/frameworks/Microsoft/KeyVault.qll | 153 ++++++++++++++++++ .../frameworks/vaults/Vaults.expected | 5 + .../library-tests/frameworks/vaults/Vaults.ql | 10 ++ .../library-tests/frameworks/vaults/app.bicep | 37 +++++ 5 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 ql/lib/codeql/bicep/frameworks/Microsoft/KeyVault.qll create mode 100644 ql/test/library-tests/frameworks/vaults/Vaults.expected create mode 100644 ql/test/library-tests/frameworks/vaults/Vaults.ql create mode 100644 ql/test/library-tests/frameworks/vaults/app.bicep diff --git a/ql/lib/codeql/bicep/Frameworks.qll b/ql/lib/codeql/bicep/Frameworks.qll index 201e80b..e6993a8 100644 --- a/ql/lib/codeql/bicep/Frameworks.qll +++ b/ql/lib/codeql/bicep/Frameworks.qll @@ -1,4 +1,5 @@ import frameworks.Microsoft.Compute import frameworks.Microsoft.Network import frameworks.Microsoft.Storage -import frameworks.Microsoft.Databases \ No newline at end of file +import frameworks.Microsoft.Databases +import frameworks.Microsoft.KeyVault \ No newline at end of file diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/KeyVault.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/KeyVault.qll new file mode 100644 index 0000000..eaa12be --- /dev/null +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/KeyVault.qll @@ -0,0 +1,153 @@ +private import bicep +private import codeql.bicep.Concepts + +module KeyVault { + class VaultResource extends Resource { + /** + * Constructs a VaultResource for any Microsoft.KeyVault resource type. + */ + VaultResource() { this.getResourceType().regexpMatch("^Microsoft.KeyVault/.*") } + + string tenantId() { result = this.getProperties().getTenantId().getValue() } + + KeyVaultProperties::Properties getProperties() { result = this.getProperty("properties") } + + KeyVaultProperties::AccessPolicy getAccessPolicies() { + result = this.getProperties().getAccessPolicies() + } + + override string toString() { result = "Key Vault Resource" } + } + + class PublicVaultResource extends PublicResource { + private VaultResource vaultResource; + + /** + * Constructs a PublicVaultResource for any Microsoft.KeyVault resource type + * that has public network access enabled. + */ + PublicVaultResource() { + vaultResource.getProperties().publicNetworkAccess() = "Enabled" and + this = vaultResource + } + + override Expr getPublicAccessProperty() { + result = vaultResource.getProperties().getPublicNetworkAccess() + } + + override string toString() { result = "Public Key Vault Resource" } + } + + module KeyVaultProperties { + /** + * The properties object for the Microsoft.KeyVault/vaults type. + */ + class Properties extends Object { + private VaultResource vaultResource; + + /** + * Constructs a Properties object for the given Key Vault resource. + */ + Properties() { this = vaultResource.getProperty("properties") } + + /** + * Returns the parent VaultResource. + */ + VaultResource getVaultResource() { result = vaultResource } + + StringLiteral getTenantId() { result = this.getProperty("tenantId") } + + string tenantId() { result = this.getTenantId().getValue() } + + StringLiteral getCreateMode() { result = this.getProperty("createMode") } + + string createMode() { result = this.getCreateMode().getValue() } + + Boolean getEnabledForDeployment() { result = this.getProperty("enabledForDeployment") } + + boolean enabledForDeployment() { result = this.getEnabledForDeployment().getBool() } + + Boolean getEnabledForDiskEncryption() { + result = this.getProperty("enabledForDiskEncryption") + } + + boolean enabledForDiskEncryption() { result = this.getEnabledForDiskEncryption().getBool() } + + Boolean getEnabledForTemplateDeployment() { + result = this.getProperty("enabledForTemplateDeployment") + } + + boolean enabledForTemplateDeployment() { + result = this.getEnabledForTemplateDeployment().getBool() + } + + Boolean getSoftDeleteEnabled() { result = this.getProperty("softDeleteEnabled") } + + boolean softDeleteEnabled() { result = this.getSoftDeleteEnabled().getBool() } + + Boolean getPurgeProtectionEnabled() { result = this.getProperty("purgeProtectionEnabled") } + + boolean purgeProtectionEnabled() { result = this.getPurgeProtectionEnabled().getBool() } + + StringLiteral getPublicNetworkAccess() { result = this.getProperty("publicNetworkAccess") } + + string publicNetworkAccess() { result = this.getPublicNetworkAccess().getValue() } + + AccessPolicy getAccessPolicies() { + result = this.getProperty("accessPolicies").(Array).getElements() + } + + AccessPolicy getAccessPolicy(int index) { + result = this.getProperty("accessPolicies").(Array).getElement(index) + } + } + + class AccessPolicy extends Object { + private KeyVaultProperties::Properties properties; + + /** + * Constructs an AccessPolicy object for the given Key Vault properties. + */ + AccessPolicy() { this = properties.getProperty("accessPolicies").(Array).getElements() } + + /** + * Returns the tenant ID of the access policy. + */ + string getTenantId() { result = this.getProperty("tenantId").(StringLiteral).getValue() } + + /** + * Returns the object ID of the access policy. + */ + string getObjectId() { result = this.getProperty("objectId").(StringLiteral).getValue() } + + string toString() { result = "AccessPolicy" } + } + + class AccessPolicyPermissions extends Object { + private AccessPolicy accessPolicy; + + /** + * Constructs an AccessPolicyPermissions object for the given access policy. + */ + AccessPolicyPermissions() { this = accessPolicy.getProperty("permissions") } + + Array getCertificates() { result = this.getProperty("certificates") } + + StringLiteral getCertificate(int index) { result = this.getCertificates().getElement(index) } + + Array getKeys() { result = this.getProperty("keys") } + + StringLiteral getKey(int index) { result = this.getKeys().getElement(index) } + + Array getSecrets() { result = this.getProperty("secrets") } + + StringLiteral getSecret(int index) { result = this.getSecrets().getElement(index) } + + Array getStorages() { result = this.getProperty("storage") } + + StringLiteral getStorage(int index) { result = this.getStorages().getElement(index) } + + string toString() { result = "AccessPolicyPermissions" } + } + } +} diff --git a/ql/test/library-tests/frameworks/vaults/Vaults.expected b/ql/test/library-tests/frameworks/vaults/Vaults.expected new file mode 100644 index 0000000..50660b9 --- /dev/null +++ b/ql/test/library-tests/frameworks/vaults/Vaults.expected @@ -0,0 +1,5 @@ +keyvault +| app.bicep:1:1:37:1 | Key Vault Resource | +keyvaultPolicies +| app.bicep:1:1:37:1 | Key Vault Resource | app.bicep:11:7:19:7 | AccessPolicy | +| app.bicep:1:1:37:1 | Key Vault Resource | app.bicep:20:7:28:7 | AccessPolicy | diff --git a/ql/test/library-tests/frameworks/vaults/Vaults.ql b/ql/test/library-tests/frameworks/vaults/Vaults.ql new file mode 100644 index 0000000..e2efa98 --- /dev/null +++ b/ql/test/library-tests/frameworks/vaults/Vaults.ql @@ -0,0 +1,10 @@ +import bicep + +query predicate keyvault(KeyVault::VaultResource vault) { + any() +} + +query predicate keyvaultPolicies(KeyVault::VaultResource vault, KeyVault::KeyVaultProperties::AccessPolicy policy) { + policy = vault.getAccessPolicies() + +} diff --git a/ql/test/library-tests/frameworks/vaults/app.bicep b/ql/test/library-tests/frameworks/vaults/app.bicep new file mode 100644 index 0000000..c7ee40d --- /dev/null +++ b/ql/test/library-tests/frameworks/vaults/app.bicep @@ -0,0 +1,37 @@ +resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = { + name: 'mykeyvault' + location: 'eastus' + properties: { + tenantId: '00000000-0000-0000-0000-000000000000' + sku: { + family: 'A' + name: 'standard' + } + accessPolicies: [ + { + tenantId: '00000000-0000-0000-0000-000000000000' + objectId: '11111111-1111-1111-1111-111111111111' + permissions: { + keys: [ 'get', 'list' ] + secrets: [ 'get' ] + certificates: [] + } + }, + { + tenantId: '00000000-0000-0000-0000-000000000000' + objectId: '22222222-2222-2222-2222-222222222222' + permissions: { + keys: [ 'get' ] + secrets: [ 'get', 'set' ] + certificates: [ 'get' ] + } + } + ] + enabledForDeployment: false + enabledForDiskEncryption: false + enabledForTemplateDeployment: false + enableSoftDelete: true + enablePurgeProtection: true + publicNetworkAccess: 'Disabled' // Recommended: restrict public access + } +} \ No newline at end of file From 23eafa12546438b24261f0406e096f62efa88e22 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Wed, 11 Jun 2025 12:25:50 +0100 Subject: [PATCH 2/2] feat: Add Network ACL support --- .../bicep/frameworks/Microsoft/KeyVault.qll | 9 +++ .../bicep/frameworks/Microsoft/Network.qll | 56 +++++++++++++++++++ .../frameworks/vaults/Vaults.expected | 8 ++- .../library-tests/frameworks/vaults/Vaults.ql | 15 +++-- .../library-tests/frameworks/vaults/app.bicep | 14 +++++ 5 files changed, 94 insertions(+), 8 deletions(-) diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/KeyVault.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/KeyVault.qll index eaa12be..138d175 100644 --- a/ql/lib/codeql/bicep/frameworks/Microsoft/KeyVault.qll +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/KeyVault.qll @@ -1,5 +1,6 @@ private import bicep private import codeql.bicep.Concepts +private import Network module KeyVault { class VaultResource extends Resource { @@ -16,6 +17,10 @@ module KeyVault { result = this.getProperties().getAccessPolicies() } + Network::NetworkAcl getNetworkAcls() { + result = this.getProperties().getNetworkAcls() + } + override string toString() { result = "Key Vault Resource" } } @@ -93,6 +98,10 @@ module KeyVault { string publicNetworkAccess() { result = this.getPublicNetworkAccess().getValue() } + Network::NetworkAcl getNetworkAcls() { + result = this.getProperty("networkAcls") + } + AccessPolicy getAccessPolicies() { result = this.getProperty("accessPolicies").(Array).getElements() } diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/Network.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/Network.qll index 98a1770..01f1522 100644 --- a/ql/lib/codeql/bicep/frameworks/Microsoft/Network.qll +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/Network.qll @@ -112,6 +112,62 @@ module Network { } } + + class NetworkAcl extends Object { + private Resource resource; + + NetworkAcl() { + exists(Object props | + props = resource.getProperty("properties") and + this = props.getProperty(["networkAcl", "networkAcls"]) + ) + } + + Resource getResource() { result = resource } + + StringLiteral getBypass() { + result = this.getProperty("bypass") + } + + string bypass() { + result = this.getBypass().getValue() + } + + StringLiteral getDefaultAction() { + result = this.getProperty("defaultAction") + } + + string defaultAction() { + result = this.getDefaultAction().getValue() + } + + IpRule getIpRules() { + result = this.getProperty("ipRules").(Array).getElements() + } + + string toString() { + result = "Network ACL" + } + } + + class IpRule extends Object { + private NetworkAcl acl; + + IpRule() { + this = acl.getProperty("ipRules").(Array).getElements() + } + + NetworkAcl getNetworkAcl() { result = acl } + + StringLiteral getValue() { + result = this.getProperty("value") + } + + string toString() { + result = "IP Rule" + } + } + module VirtualNetworkProperties { /** * The properties object for the Microsoft.Network/virtualNetworks/subnets type. diff --git a/ql/test/library-tests/frameworks/vaults/Vaults.expected b/ql/test/library-tests/frameworks/vaults/Vaults.expected index 50660b9..1d70f1e 100644 --- a/ql/test/library-tests/frameworks/vaults/Vaults.expected +++ b/ql/test/library-tests/frameworks/vaults/Vaults.expected @@ -1,5 +1,7 @@ keyvault -| app.bicep:1:1:37:1 | Key Vault Resource | +| app.bicep:1:1:51:1 | Key Vault Resource | keyvaultPolicies -| app.bicep:1:1:37:1 | Key Vault Resource | app.bicep:11:7:19:7 | AccessPolicy | -| app.bicep:1:1:37:1 | Key Vault Resource | app.bicep:20:7:28:7 | AccessPolicy | +| app.bicep:1:1:51:1 | Key Vault Resource | app.bicep:11:7:19:7 | AccessPolicy | +| app.bicep:1:1:51:1 | Key Vault Resource | app.bicep:20:7:28:7 | AccessPolicy | +keyvaultNetworkAcls +| app.bicep:1:1:51:1 | Key Vault Resource | app.bicep:36:18:49:5 | Network ACL | diff --git a/ql/test/library-tests/frameworks/vaults/Vaults.ql b/ql/test/library-tests/frameworks/vaults/Vaults.ql index e2efa98..0ab9ec3 100644 --- a/ql/test/library-tests/frameworks/vaults/Vaults.ql +++ b/ql/test/library-tests/frameworks/vaults/Vaults.ql @@ -1,10 +1,15 @@ import bicep -query predicate keyvault(KeyVault::VaultResource vault) { - any() -} +query predicate keyvault(KeyVault::VaultResource vault) { any() } -query predicate keyvaultPolicies(KeyVault::VaultResource vault, KeyVault::KeyVaultProperties::AccessPolicy policy) { +query predicate keyvaultPolicies( + KeyVault::VaultResource vault, KeyVault::KeyVaultProperties::AccessPolicy policy +) { policy = vault.getAccessPolicies() - +} + +query predicate keyvaultNetworkAcls( + KeyVault::VaultResource vault, Network::NetworkAcl networkAcl +) { + networkAcl = vault.getNetworkAcls() } diff --git a/ql/test/library-tests/frameworks/vaults/app.bicep b/ql/test/library-tests/frameworks/vaults/app.bicep index c7ee40d..c954617 100644 --- a/ql/test/library-tests/frameworks/vaults/app.bicep +++ b/ql/test/library-tests/frameworks/vaults/app.bicep @@ -33,5 +33,19 @@ resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = { enableSoftDelete: true enablePurgeProtection: true publicNetworkAccess: 'Disabled' // Recommended: restrict public access + networkAcls: { + bypass: 'AzureServices' + defaultAction: 'Deny' + ipRules: [ + { + value: '203.0.113.0/24' + } + ] + virtualNetworkRules: [ + { + id: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myrg/providers/Microsoft.Network/virtualNetworks/myvnet/subnets/mysubnet' + } + ] + } } } \ No newline at end of file