From 9dc7e8d554800782465cfa6d664c20fd5e737615 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Tue, 8 Jul 2025 11:59:59 +0100 Subject: [PATCH 1/7] fix: Update Web framework support --- .../codeql/bicep/frameworks/Microsoft/Web.qll | 105 ++++++++++++------ 1 file changed, 72 insertions(+), 33 deletions(-) diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll index 1db4534..a5e9ea2 100644 --- a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll @@ -63,7 +63,7 @@ module Web { /** * Returns the kind of App Service as a string. */ - string kind() { + string kind() { exists(StringLiteral kind | kind = this.getKind() and result = kind.getValue()) } @@ -75,22 +75,17 @@ module Web { /** * Checks if the site is a regular web app. */ - predicate isWebApp() { + predicate isWebApp() { this.kind().regexpMatch(".*app.*") and not this.isFunctionApp() } - /** - * Gets the HTTPS-only flag for the App Service. - */ - BooleanLiteral getHttpsOnly() { result = this.getProperty("httpsOnly") } - /** * Returns true if HTTPS-only setting is enabled. */ predicate isHttpsOnly() { exists(BooleanLiteral httpsOnly | - httpsOnly = this.getHttpsOnly() and + httpsOnly = this.getProperties().getHttpsOnly() and httpsOnly.getBool() = true ) } @@ -158,7 +153,9 @@ module Web { /** * Gets the keyVaultReferenceIdentity. */ - StringLiteral getKeyVaultReferenceIdentity() { result = this.getProperty("keyVaultReferenceIdentity") } + StringLiteral getKeyVaultReferenceIdentity() { + result = this.getProperty("keyVaultReferenceIdentity") + } /** * Gets the redundancyMode. @@ -168,7 +165,9 @@ module Web { /** * Gets the storageAccountRequired flag. */ - BooleanLiteral getStorageAccountRequired() { result = this.getProperty("storageAccountRequired") } + BooleanLiteral getStorageAccountRequired() { + result = this.getProperty("storageAccountRequired") + } /** * Returns true if a storage account is required. @@ -183,7 +182,9 @@ module Web { /** * Gets the virtualNetworkSubnetId. */ - StringLiteral getVirtualNetworkSubnetId() { result = this.getProperty("virtualNetworkSubnetId") } + StringLiteral getVirtualNetworkSubnetId() { + result = this.getProperty("virtualNetworkSubnetId") + } override string toString() { result = "AppService[" + this.getIdentifier().getName() + "]" } } @@ -296,7 +297,7 @@ module Web { */ string getParentSiteName() { // Parse from the resource name which is in the format "siteName/slotName" - exists(string fullName | + exists(string fullName | fullName = this.getName() and result = fullName.regexpCapture("([^/]+)/.*", 1) ) @@ -307,7 +308,7 @@ module Web { */ string getSlotName() { // Parse from the resource name which is in the format "siteName/slotName" - exists(string fullName | + exists(string fullName | fullName = this.getName() and result = fullName.regexpCapture("[^/]+/(.*)", 1) ) @@ -352,12 +353,16 @@ module Web { /** * Constructs a HostingEnvironmentsResource for Microsoft.Web/hostingEnvironments resources. */ - HostingEnvironmentsResource() { this.getResourceType().regexpMatch("^Microsoft.Web/hostingEnvironments@.*") } + HostingEnvironmentsResource() { + this.getResourceType().regexpMatch("^Microsoft.Web/hostingEnvironments@.*") + } /** * Gets the properties object for the App Service Environment. */ - HostingEnvironmentsProperties::Properties getProperties() { result = this.getProperty("properties") } + HostingEnvironmentsProperties::Properties getProperties() { + result = this.getProperty("properties") + } /** * Gets the kind of the App Service Environment. @@ -369,7 +374,9 @@ module Web { */ string kind() { result = this.getKind().getValue() } - override string toString() { result = "AppServiceEnvironment[" + this.getIdentifier().getName() + "]" } + override string toString() { + result = "AppServiceEnvironment[" + this.getIdentifier().getName() + "]" + } } /** @@ -400,17 +407,26 @@ module Web { /** * Gets the hostingEnvironmentProfile. */ - Object getHostingEnvironmentProfile() { result = this.getProperty("hostingEnvironmentProfile") } + Object getHostingEnvironmentProfile() { + result = this.getProperty("hostingEnvironmentProfile") + } /** * Gets the public network access setting. */ StringLiteral getPublicNetworkAccess() { result = this.getProperty("publicNetworkAccess") } + /** + * Gets the HTTPS-only flag for the App Service. + */ + BooleanLiteral getHttpsOnly() { result = this.getProperty("httpsOnly") } + /** * Gets the virtualNetworkSubnetId. */ - StringLiteral getVirtualNetworkSubnetId() { result = this.getProperty("virtualNetworkSubnetId") } + StringLiteral getVirtualNetworkSubnetId() { + result = this.getProperty("virtualNetworkSubnetId") + } /** * Gets the enabled value. @@ -431,7 +447,7 @@ module Web { * Gets the client certificate mode. */ StringLiteral getClientCertMode() { result = this.getProperty("clientCertMode") } - + /** * Returns true if client certificate is required. */ @@ -445,7 +461,9 @@ module Web { /** * Gets the client certificate exclusion paths. */ - StringLiteral getClientCertExclusionPaths() { result = this.getProperty("clientCertExclusionPaths") } + StringLiteral getClientCertExclusionPaths() { + result = this.getProperty("clientCertExclusionPaths") + } /** * Gets the container size. @@ -455,7 +473,9 @@ module Web { /** * Gets the custom domain verification ID. */ - StringLiteral getCustomDomainVerificationId() { result = this.getProperty("customDomainVerificationId") } + StringLiteral getCustomDomainVerificationId() { + result = this.getProperty("customDomainVerificationId") + } /** * Gets the daily memory time quota. @@ -509,7 +529,9 @@ module Web { /** * Gets whether remote debugging is enabled. */ - BooleanLiteral getRemoteDebuggingEnabled() { result = this.getProperty("remoteDebuggingEnabled") } + BooleanLiteral getRemoteDebuggingEnabled() { + result = this.getProperty("remoteDebuggingEnabled") + } /** * Returns true if remote debugging is enabled. @@ -524,7 +546,9 @@ module Web { /** * Gets the remote debugging version. */ - StringLiteral getRemoteDebuggingVersion() { result = this.getProperty("remoteDebuggingVersion") } + StringLiteral getRemoteDebuggingVersion() { + result = this.getProperty("remoteDebuggingVersion") + } /** * Gets whether HTTP 2.0 is enabled. @@ -546,6 +570,11 @@ module Web { */ BooleanLiteral getAlwaysOn() { result = this.getProperty("alwaysOn") } + /** + * Returns the Always On setting as a boolean. + */ + boolean alwaysOn() { result = this.getAlwaysOn().getBool() } + /** * Returns true if Always On is enabled. */ @@ -657,9 +686,7 @@ module Web { /** * Gets a certificate by index. */ - Object getCertificate(int index) { - result = this.getElement(index) - } + Object getCertificate(int index) { result = this.getElement(index) } string toString() { result = "HttpsCertificates" } } @@ -728,12 +755,16 @@ module Web { /** * Gets the staging environment policy. */ - StringLiteral getStagingEnvironmentPolicy() { result = this.getProperty("stagingEnvironmentPolicy") } + StringLiteral getStagingEnvironmentPolicy() { + result = this.getProperty("stagingEnvironmentPolicy") + } /** * Gets whether private endpoint connections are allowed. */ - BooleanLiteral getAllowConfigFileUpdates() { result = this.getProperty("allowConfigFileUpdates") } + BooleanLiteral getAllowConfigFileUpdates() { + result = this.getProperty("allowConfigFileUpdates") + } /** * Returns true if config file updates are allowed. @@ -763,7 +794,9 @@ module Web { /** * Gets whether private endpoint connections are allowed. */ - BooleanLiteral getAllowPrivateEndpoints() { result = this.getProperty("allowPrivateEndpoints") } + BooleanLiteral getAllowPrivateEndpoints() { + result = this.getProperty("allowPrivateEndpoints") + } /** * Returns true if private endpoints are allowed. @@ -817,7 +850,9 @@ module Web { /** * Gets the internal load balancing mode. */ - StringLiteral getInternalLoadBalancingMode() { result = this.getProperty("internalLoadBalancingMode") } + StringLiteral getInternalLoadBalancingMode() { + result = this.getProperty("internalLoadBalancingMode") + } /** * Gets the cluster settings. @@ -827,7 +862,9 @@ module Web { /** * Gets the virtual network configuration. */ - VnetConfiguration getVirtualNetworkProfile() { result = this.getProperty("virtualNetworkProfile") } + VnetConfiguration getVirtualNetworkProfile() { + result = this.getProperty("virtualNetworkProfile") + } override string toString() { result = "HostingEnvironmentProperties" } } @@ -960,7 +997,9 @@ module Web { /** * Gets the maximum number of workers. */ - Number getMaximumElasticWorkerCount() { result = this.getProperty("maximumElasticWorkerCount") } + Number getMaximumElasticWorkerCount() { + result = this.getProperty("maximumElasticWorkerCount") + } /** * Returns the maximum number of workers as an integer. @@ -970,4 +1009,4 @@ module Web { override string toString() { result = "ServerFarmProperties" } } } -} \ No newline at end of file +} From 826274dd924ef00aa207134e45671a3b2ddb17b8 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Tue, 8 Jul 2025 12:00:47 +0100 Subject: [PATCH 2/7] feat(security): Add security checks for Azure Web Apps including Always On, public network access, client certificate requirements, remote debugging, and HTTPS-only settings --- ql/src/reliability/WebAppAlwaysOnDisabled.md | 65 +++++++++++++++++++ ql/src/reliability/WebAppAlwaysOnDisabled.ql | 25 +++++++ .../CWE-284/WebAppPublicNetworkAccess.md | 65 +++++++++++++++++++ .../CWE-284/WebAppPublicNetworkAccess.ql | 27 ++++++++ .../CWE-295/WebAppMissingClientCert.md | 58 +++++++++++++++++ .../CWE-295/WebAppMissingClientCert.ql | 31 +++++++++ .../CWE-306/WebAppRemoteDebuggingEnabled.md | 59 +++++++++++++++++ .../CWE-306/WebAppRemoteDebuggingEnabled.ql | 22 +++++++ .../security/CWE-319/SitesWithoutHttpsOnly.md | 55 ++++++++++++++++ .../security/CWE-319/SitesWithoutHttpsOnly.ql | 22 +++++++ 10 files changed, 429 insertions(+) create mode 100644 ql/src/reliability/WebAppAlwaysOnDisabled.md create mode 100644 ql/src/reliability/WebAppAlwaysOnDisabled.ql create mode 100644 ql/src/security/CWE-284/WebAppPublicNetworkAccess.md create mode 100644 ql/src/security/CWE-284/WebAppPublicNetworkAccess.ql create mode 100644 ql/src/security/CWE-295/WebAppMissingClientCert.md create mode 100644 ql/src/security/CWE-295/WebAppMissingClientCert.ql create mode 100644 ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.md create mode 100644 ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.ql create mode 100644 ql/src/security/CWE-319/SitesWithoutHttpsOnly.md create mode 100644 ql/src/security/CWE-319/SitesWithoutHttpsOnly.ql diff --git a/ql/src/reliability/WebAppAlwaysOnDisabled.md b/ql/src/reliability/WebAppAlwaysOnDisabled.md new file mode 100644 index 0000000..48ca6bf --- /dev/null +++ b/ql/src/reliability/WebAppAlwaysOnDisabled.md @@ -0,0 +1,65 @@ +# Web App without Always On enabled + +Azure Web Apps should have the "Always On" setting enabled in production environments to ensure reliability, performance, and security. When Always On is disabled, the application may experience cold start delays and periodic shutdowns that could impact availability and security. + +## Recommendation + +Enable the "Always On" setting for all production Azure Web Apps: + +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + alwaysOn: true // Enable Always On for reliability and security + } + } +} +``` + +## Example + +### Suboptimal configuration + +```bicep +resource webAppWithoutAlwaysOn 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + // Always On is not explicitly enabled, which can lead to + // application shutdowns and cold starts + } + } +} +``` + +### Recommended configuration + +```bicep +resource webAppWithAlwaysOn 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + alwaysOn: true // Explicitly enable Always On + } + } +} +``` + +## Why this is important + +When "Always On" is disabled: +- The application can be unloaded after a period of inactivity +- Cold starts can cause delays for users and create availability issues +- Periodic recycling can interrupt background processes +- Attackers could potentially exploit behavior differences between cold and warm instances + +## References +* [Configure an App Service app](https://learn.microsoft.com/en-us/azure/app-service/configure-common) +* [Azure App Service plan overview](https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans) diff --git a/ql/src/reliability/WebAppAlwaysOnDisabled.ql b/ql/src/reliability/WebAppAlwaysOnDisabled.ql new file mode 100644 index 0000000..d94404d --- /dev/null +++ b/ql/src/reliability/WebAppAlwaysOnDisabled.ql @@ -0,0 +1,25 @@ +/** + * @name Web App without Always On enabled + * @description Azure Web Apps should have Always On enabled to ensure reliability and security. + * @kind problem + * @problem.severity warning + * @security-severity 4.0 + * @precision high + * @id bicep/webapp-always-on-disabled + * @tags security + * bicep + * azure + * reliability + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site, Web::SitesProperties::SiteConfig config +where + config = site.getProperties().getSiteConfig() and + not config.isAlwaysOn() and + // Only apply to production web apps, not function apps + site.isWebApp() and + not site.isFunctionApp() +select site, "Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts." diff --git a/ql/src/security/CWE-284/WebAppPublicNetworkAccess.md b/ql/src/security/CWE-284/WebAppPublicNetworkAccess.md new file mode 100644 index 0000000..5a6378f --- /dev/null +++ b/ql/src/security/CWE-284/WebAppPublicNetworkAccess.md @@ -0,0 +1,65 @@ +# Web App with unrestricted public access + +Azure Web Apps with unrestricted public network access are potentially vulnerable to attacks from the internet. For sensitive applications, restricting network access by using Virtual Network integration or by disabling public network access provides an additional layer of security. + +## Recommendation + +Restrict public network access to your Azure Web App by either: + +1. Integrating with Azure Virtual Network: +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + virtualNetworkSubnetId: subnet.id // Add VNet integration + } +} +``` + +2. Or by explicitly disabling public network access (for critical applications): +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + publicNetworkAccess: 'Disabled' // Disable public network access + } +} +``` + +## Example + +### Insecure configuration + +```bicep +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecureApp' + location: location + properties: { + serverFarmId: appServicePlan.id + // No network restrictions - accessible from anywhere + } +} +``` + +### Secure configuration + +```bicep +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secureApp' + location: location + properties: { + serverFarmId: appServicePlan.id + publicNetworkAccess: 'Disabled' + virtualNetworkSubnetId: resourceId('Microsoft.Network/virtualNetworks/subnets', 'myVNet', 'mySubnet') + } +} +``` + +## References +* [Azure App Service networking features](https://learn.microsoft.com/en-us/azure/app-service/networking-features) +* [Configure regional VNet integration](https://learn.microsoft.com/en-us/azure/app-service/configure-vnet-integration-enable) +* [CWE-284: Improper Access Control](https://cwe.mitre.org/data/definitions/284.html) diff --git a/ql/src/security/CWE-284/WebAppPublicNetworkAccess.ql b/ql/src/security/CWE-284/WebAppPublicNetworkAccess.ql new file mode 100644 index 0000000..0d028b7 --- /dev/null +++ b/ql/src/security/CWE-284/WebAppPublicNetworkAccess.ql @@ -0,0 +1,27 @@ +/** + * @name Web App with unrestricted public access + * @description Azure Web Apps should restrict public network access to enhance security. + * @kind problem + * @problem.severity warning + * @security-severity 6.5 + * @precision high + * @id bicep/sites-public-network-access + * @tags security + * bicep + * azure + * CWE-284 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site, Web::SitesProperties::Properties props +where + props = site.getProperties() and + // Check if the site has public network access enabled (default) and no VNet integration + props.isPublicNetworkAccessEnabled() and + not exists(StringLiteral vnetSubnetId | + vnetSubnetId = site.getVirtualNetworkSubnetId() or + vnetSubnetId = props.getVirtualNetworkSubnetId() + ) +select site, "Azure Web App has unrestricted public network access without VNet integration, potentially increasing attack surface." diff --git a/ql/src/security/CWE-295/WebAppMissingClientCert.md b/ql/src/security/CWE-295/WebAppMissingClientCert.md new file mode 100644 index 0000000..8adcd3b --- /dev/null +++ b/ql/src/security/CWE-295/WebAppMissingClientCert.md @@ -0,0 +1,58 @@ +# Web App without Client Certificate requirement + +Azure Web Apps that handle sensitive operations should consider requiring client certificates for mutual TLS authentication. Client certificates provide an additional layer of security by ensuring that only authenticated clients with valid certificates can access the application. + +## Recommendation + +For applications handling sensitive information or operations, enable and require client certificates: + +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'mySecureWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + clientCertMode: 'Required' // Enforce client certificate validation + } + httpsOnly: true +} +``` + +## Example + +### Incomplete configuration + +```bicep +resource webAppWithIncompleteConfig 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + // Client certificates are enabled but not required + clientCertEnabled: true + // Missing clientCertMode: 'Required' + } + httpsOnly: true +} +``` + +### Secure configuration + +```bicep +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'mySecureWebApp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + clientCertMode: 'Required' // Certificates are required + } + httpsOnly: true +} +``` + +## References +* [Configure TLS mutual authentication for Azure App Service](https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth) +* [Microsoft.Web/sites resource type](https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites) +* [CWE-295: Improper Certificate Validation](https://cwe.mitre.org/data/definitions/295.html) diff --git a/ql/src/security/CWE-295/WebAppMissingClientCert.ql b/ql/src/security/CWE-295/WebAppMissingClientCert.ql new file mode 100644 index 0000000..cdea4d6 --- /dev/null +++ b/ql/src/security/CWE-295/WebAppMissingClientCert.ql @@ -0,0 +1,31 @@ +/** + * @name Web App without Client Certificate requirement + * @description Azure Web Apps handling sensitive operations should require client certificates for additional authentication. + * @kind problem + * @problem.severity warning + * @security-severity 5.0 + * @precision medium + * @id bicep/webapp-missing-client-cert + * @tags security + * bicep + * azure + * CWE-295 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site +where + // Site has HTTPS enabled (showing security awareness) + site.isHttpsOnly() and + ( + // But doesn't have client certificate enabled at the site level + not site.isClientCertEnabled() or + // Or has it enabled but not set to required in properties + ( + site.isClientCertEnabled() and + not site.getProperties().isClientCertRequired() + ) + ) +select site, "Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication." diff --git a/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.md b/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.md new file mode 100644 index 0000000..b19dfb5 --- /dev/null +++ b/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.md @@ -0,0 +1,59 @@ +# Web App with Remote Debugging enabled + +Remote debugging should be disabled in production Azure Web Apps. When remote debugging is enabled in a production environment, it can expose sensitive information and potentially allow unauthorized access to your application. + +## Recommendation + +Disable remote debugging in production environments: + +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + siteConfig: { + // Ensure remote debugging is disabled + remoteDebuggingEnabled: false + // or omit the property entirely as it defaults to false + } + } +} +``` + +## Example + +### Insecure configuration + +```bicep +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecureApp' + location: location + properties: { + siteConfig: { + // Remote debugging should never be enabled in production + remoteDebuggingEnabled: true + remoteDebuggingVersion: 'VS2019' + } + } +} +``` + +### Secure configuration + +```bicep +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secureApp' + location: location + properties: { + siteConfig: { + // Remote debugging is explicitly disabled + remoteDebuggingEnabled: false + } + } +} +``` + +## References +* [Azure App Service security best practices](https://learn.microsoft.com/en-us/azure/app-service/security-recommendations) +* [Microsoft.Web/sites resource type](https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites) +* [CWE-306: Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html) diff --git a/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.ql b/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.ql new file mode 100644 index 0000000..99fac15 --- /dev/null +++ b/ql/src/security/CWE-306/WebAppRemoteDebuggingEnabled.ql @@ -0,0 +1,22 @@ +/** + * @name Web App with Remote Debugging enabled + * @description Remote debugging should be disabled in production web apps to prevent unauthorized access. + * @kind problem + * @problem.severity error + * @security-severity 8.0 + * @precision high + * @id bicep/webapp-remote-debugging-enabled + * @tags security + * bicep + * azure + * CWE-306 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site, Web::SitesProperties::SiteConfig config +where + config = site.getProperties().getSiteConfig() and + config.isRemoteDebuggingEnabled() +select site, "Azure Web App has remote debugging enabled, which can expose sensitive debugging information." diff --git a/ql/src/security/CWE-319/SitesWithoutHttpsOnly.md b/ql/src/security/CWE-319/SitesWithoutHttpsOnly.md new file mode 100644 index 0000000..a5090db --- /dev/null +++ b/ql/src/security/CWE-319/SitesWithoutHttpsOnly.md @@ -0,0 +1,55 @@ +# Web App without HTTPS-only setting + +Azure Web Apps should have the HTTPS-only setting enabled to enforce secure communications. When the HTTPS-only flag is not set, the web app can accept connections over unencrypted HTTP, potentially exposing sensitive information. + +## Recommendation + +Enable the HTTPS-only setting for all Azure Web Apps to ensure that all traffic is redirected to HTTPS. + +In Bicep files, set the `httpsOnly` property to `true`: + +```bicep +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'myWebApp' + location: location + properties: { + // other properties + } + // Enable HTTPS-only mode + httpsOnly: true +} +``` + +## Example + +### Insecure configuration + +```bicep +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecureApp' + location: location + properties: { + serverFarmId: appServicePlan.id + } + // HTTPS-only is not set, defaulting to false +} +``` + +### Secure configuration + +```bicep +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secureApp' + location: location + properties: { + serverFarmId: appServicePlan.id + } + // HTTPS-only is explicitly enabled + httpsOnly: true +} +``` + +## References +* [Azure App Service secure settings](https://learn.microsoft.com/en-us/azure/app-service/overview-security#https-and-certificates) +* [Microsoft.Web/sites resource type](https://learn.microsoft.com/en-us/azure/templates/microsoft.web/sites) +* [CWE-319: Cleartext Transmission of Sensitive Information](https://cwe.mitre.org/data/definitions/319.html) diff --git a/ql/src/security/CWE-319/SitesWithoutHttpsOnly.ql b/ql/src/security/CWE-319/SitesWithoutHttpsOnly.ql new file mode 100644 index 0000000..2f1832a --- /dev/null +++ b/ql/src/security/CWE-319/SitesWithoutHttpsOnly.ql @@ -0,0 +1,22 @@ +/** + * @name Web App without HTTPS-only setting + * @description Azure Web Apps should enforce HTTPS-only mode to secure communications. + * @kind problem + * @problem.severity error + * @security-severity 7.5 + * @precision high + * @id bicep/sites-without-https-only + * @tags security + * bicep + * azure + * CWE-319 + */ + +import bicep +import codeql.bicep.frameworks.Microsoft.Web + +from Web::SitesResource site +where + // Find all sites without httpsOnly set to true + not site.isHttpsOnly() +select site, "Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections." \ No newline at end of file From 65d86d6cc0ef6ce217d81c221370896e5b20a97b Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Tue, 8 Jul 2025 12:01:04 +0100 Subject: [PATCH 3/7] feat(tests): Add test cases for Azure Web App security configurations including Always On, public network access, client certificate requirements, and HTTPS-only settings --- .../WebAppAlwaysOnDisabled.expected | 2 + .../WebAppAlwaysOnDisabled.qlref | 1 + .../WebAppAlwaysOnDisabled/app.bicep | 66 +++++++++++++ .../WebAppPublicNetworkAccess.expected | 2 + .../WebAppPublicNetworkAccess.qlref | 1 + .../WebAppPublicNetworkAccess/app.bicep | 96 +++++++++++++++++++ .../WebAppMissingClientCert.expected | 4 + .../WebAppMissingClientCert.qlref | 1 + .../CWE-295/WebAppMissingClientCert/app.bicep | 77 +++++++++++++++ .../SitesWithoutHttpsOnly.expected | 2 + .../SitesWithoutHttpsOnly.qlref | 1 + .../CWE-319/SitesWithoutHttpsOnly/app.bicep | 84 ++++++++++++++++ 12 files changed, 337 insertions(+) create mode 100644 ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected create mode 100644 ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref create mode 100644 ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/app.bicep create mode 100644 ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.expected create mode 100644 ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.qlref create mode 100644 ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/app.bicep create mode 100644 ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected create mode 100644 ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.qlref create mode 100644 ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/app.bicep create mode 100644 ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected create mode 100644 ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.qlref create mode 100644 ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep diff --git a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected new file mode 100644 index 0000000..44080aa --- /dev/null +++ b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected @@ -0,0 +1,2 @@ +| webapp-without-alwayson | Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts. | +| webapp-with-alwayson-disabled | Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts. | diff --git a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref new file mode 100644 index 0000000..06359de --- /dev/null +++ b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref @@ -0,0 +1 @@ +security/reliability/WebAppAlwaysOnDisabled.ql diff --git a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/app.bicep b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/app.bicep new file mode 100644 index 0000000..4e8c110 --- /dev/null +++ b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/app.bicep @@ -0,0 +1,66 @@ +// This is a test file for the WebAppAlwaysOnDisabled query +// It contains examples of secure and insecure configurations + +param location string = resourceGroup().location + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: 'app-plan-test' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } +} + +// Insecure: Web App without Always On enabled (Default) +resource webAppWithoutAlwaysOn 'Microsoft.Web/sites@2022-03-01' = { + name: 'webapp-without-alwayson' + location: location + kind: 'app' // Specifies it's a regular web app + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + // Always On is not explicitly set, defaults to false + } + } +} + +// Insecure: Web App with Always On explicitly disabled +resource webAppWithAlwaysOnDisabled 'Microsoft.Web/sites@2022-03-01' = { + name: 'webapp-with-alwayson-disabled' + location: location + kind: 'app' // Specifies it's a regular web app + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + alwaysOn: false // Explicitly disabled + } + } +} + +// Secure: Web App with Always On enabled +resource webAppWithAlwaysOn 'Microsoft.Web/sites@2022-03-01' = { + name: 'webapp-with-alwayson' + location: location + kind: 'app' // Specifies it's a regular web app + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + alwaysOn: true // Explicitly enabled + } + } +} + +// Function App without Always On (should not be flagged) +resource functionApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'function-app-without-alwayson' + location: location + kind: 'functionapp' // Specifies it's a function app + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + // Always On not set for function app is acceptable + } + } +} diff --git a/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.expected b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.expected new file mode 100644 index 0000000..c41b7ce --- /dev/null +++ b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.expected @@ -0,0 +1,2 @@ +| app.bicep:46:1:54:1 | AppService[insecureWebApp] | Azure Web App has unrestricted public network access without VNet integration, potentially increasing attack surface. | +| app.bicep:57:1:65:1 | AppService[explicitlyInsecureWebApp] | Azure Web App has unrestricted public network access without VNet integration, potentially increasing attack surface. | diff --git a/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.qlref b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.qlref new file mode 100644 index 0000000..df65360 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/WebAppPublicNetworkAccess.qlref @@ -0,0 +1 @@ +security/CWE-284/WebAppPublicNetworkAccess.ql diff --git a/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/app.bicep b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/app.bicep new file mode 100644 index 0000000..b763b22 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-284/WebAppPublicNetworkAccess/app.bicep @@ -0,0 +1,96 @@ +// This is a test file for the WebAppPublicNetworkAccess query +// It contains examples of secure and insecure configurations + +param location string = resourceGroup().location + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: 'app-plan-test' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } +} + +// VNet resource +resource vnet 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: 'test-vnet' + location: location + properties: { + addressSpace: { + addressPrefixes: [ + '10.0.0.0/16' + ] + } + subnets: [ + { + name: 'app-subnet' + properties: { + addressPrefix: '10.0.1.0/24' + delegations: [ + { + name: 'delegation' + properties: { + serviceName: 'Microsoft.Web/serverfarms' + } + } + ] + } + } + ] + } +} + +// Insecure: Web App with default public network access (enabled) and no VNet integration +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecure-public-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + // No public network access restrictions + // No VNet integration + } +} + +// Insecure: Web App with explicitly enabled public network access and no VNet integration +resource explicitlyInsecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'explicitly-insecure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + publicNetworkAccess: 'Enabled' // Explicitly allowing public access + // No VNet integration + } +} + +// Secure: Web App with VNet integration +resource secureWithVNetWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-vnet-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + // Public network access not explicitly disabled, but has VNet integration + virtualNetworkSubnetId: '${vnet.id}/subnets/app-subnet' + } +} + +// Secure: Web App with disabled public network access +resource secureNoPublicWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-no-public-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + publicNetworkAccess: 'Disabled' // Explicitly disabling public access + } +} + +// Secure: Web App with VNet integration at the properties level +resource secureWithPropsVNetWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-props-vnet-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + virtualNetworkSubnetId: '${vnet.id}/subnets/app-subnet' + } +} diff --git a/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected new file mode 100644 index 0000000..67e877f --- /dev/null +++ b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected @@ -0,0 +1,4 @@ +| app.bicep:18:1:26:1 | AppService[insecureWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | +| app.bicep:30:1:39:1 | AppService[partiallySecureWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | +| app.bicep:43:1:52:1 | AppService[explicitlyOptionalWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | +| app.bicep:56:1:65:1 | AppService[secureWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | diff --git a/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.qlref b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.qlref new file mode 100644 index 0000000..dd57955 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.qlref @@ -0,0 +1 @@ +security/CWE-295/WebAppMissingClientCert.ql diff --git a/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/app.bicep b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/app.bicep new file mode 100644 index 0000000..fc972b7 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/app.bicep @@ -0,0 +1,77 @@ +// This is a test file for the WebAppMissingClientCert query +// It contains examples of secure and insecure configurations + +param location string = resourceGroup().location + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: 'app-plan-test' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } +} + +// Insecure: Web App with HTTPS-Only but no client cert configuration +// This should be flagged by the query +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecure-webapp-no-clientcert' + location: location + properties: { + serverFarmId: appServicePlan.id + httpsOnly: true // HTTPS is enabled but no client cert + // No client cert configuration + } +} + +// Insecure: Web App with HTTPS-Only and client cert enabled but not required +// This should be flagged by the query +resource partiallySecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'partially-secure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + httpsOnly: true + // clientCertMode not set to Required + } +} + +// Insecure: Web App with explicit non-Required client cert mode +// This should be flagged by the query +resource explicitlyOptionalWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'explicitly-optional-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + clientCertMode: 'Optional' // Explicitly not required + httpsOnly: true + } +} + +// Secure: Web App with HTTPS-Only and required client cert +// This should NOT be flagged by the query +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + clientCertEnabled: true + clientCertMode: 'Required' // Client cert is required + httpsOnly: true + } +} + +// Web App without HTTPS-Only +// This should NOT be flagged by the query (since we only check apps with HTTPS enabled) +resource httpWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'http-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + // No client cert configuration + } + // No httpsOnly setting +} diff --git a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected new file mode 100644 index 0000000..c77a388 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected @@ -0,0 +1,2 @@ +| app.bicep:17:1:27:1 | AppService[insecureWebApp] | Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections. | +| app.bicep:30:1:37:1 | AppService[explicitlyInsecureWebApp] | Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections. | diff --git a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.qlref b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.qlref new file mode 100644 index 0000000..4d84307 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.qlref @@ -0,0 +1 @@ +security/CWE-319/SitesWithoutHttpsOnly.ql diff --git a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep new file mode 100644 index 0000000..03a83d5 --- /dev/null +++ b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep @@ -0,0 +1,84 @@ +// This is a test file for the SitesWithoutHttpsOnly query +// It contains examples of secure and insecure configurations + +param location string = resourceGroup().location + +// App Service Plan +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: 'app-plan-test' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } +} + +// Insecure: Web App without HTTPS Only +resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'insecure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + ftpsState: 'AllAllowed' // Insecure: allows non-secure FTP + } + } + // Missing httpsOnly property or set to false +} + +// Insecure: Web App with HTTPS Only explicitly set to false +resource explicitlyInsecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'explicitly-insecure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + } + httpsOnly: false // Explicitly insecure: allows HTTP +} + +// Secure: Web App with HTTPS Only enabled +resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'secure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + ftpsState: 'FtpsOnly' // Secure: only allows FTPS + } + } + httpsOnly: true // Secure: enforces HTTPS +} + +// Secure: Web App with HTTPS Only, client certs, and VNet integration +resource highlySecureWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'highly-secure-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + virtualNetworkSubnetId: '/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnet}/subnets/{subnet}' + clientCertEnabled: true + clientCertMode: 'Required' + publicNetworkAccess: 'Disabled' + siteConfig: { + ftpsState: 'Disabled' + minTlsVersion: '1.2' + remoteDebuggingEnabled: false + alwaysOn: true + } + } + httpsOnly: true +} + +// Insecure: Web App with remote debugging enabled +resource debuggableWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'debuggable-webapp' + location: location + properties: { + serverFarmId: appServicePlan.id + siteConfig: { + remoteDebuggingEnabled: true // Insecure: enables remote debugging + remoteDebuggingVersion: 'VS2019' + } + } + httpsOnly: true +} From 91de882ce2cbeb47c6e890256597cf8c320ebb06 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Tue, 8 Jul 2025 17:33:27 +0100 Subject: [PATCH 4/7] feat(Web): Enhance App Service and Slot properties with new methods and ExtendedLocation class --- ql/lib/codeql/bicep/ast/Literals.qll | 4 + .../codeql/bicep/frameworks/Microsoft/Web.qll | 639 ++++++++++++------ 2 files changed, 454 insertions(+), 189 deletions(-) diff --git a/ql/lib/codeql/bicep/ast/Literals.qll b/ql/lib/codeql/bicep/ast/Literals.qll index 5929d24..729c7fc 100644 --- a/ql/lib/codeql/bicep/ast/Literals.qll +++ b/ql/lib/codeql/bicep/ast/Literals.qll @@ -74,6 +74,10 @@ class Boolean extends Literals instanceof BooleanImpl { result = false ) } + + boolean getValue() { + result = this.getBool() + } } /** diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll index a5e9ea2..db6fe7a 100644 --- a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll @@ -14,6 +14,7 @@ * - SlotResource: Class for Microsoft.Web/sites/slots resources (Deployment Slots) * - StaticSitesResource: Class for Microsoft.Web/staticSites resources (Static Web Apps) * - HostingEnvironmentsResource: Class for Microsoft.Web/hostingEnvironments resources (App Service Environments) + * - ExtendedLocation: Class for extended location configuration */ private import bicep @@ -58,14 +59,12 @@ module Web { /** * Gets the kind of App Service (e.g., "app", "functionapp", "api"). */ - StringLiteral getKind() { result = this.getProperty("kind") } + String getKind() { result = this.getProperty("kind") } /** * Returns the kind of App Service as a string. */ - string kind() { - exists(StringLiteral kind | kind = this.getKind() and result = kind.getValue()) - } + string kind() { exists(String kind | kind = this.getKind() and result = kind.getValue()) } /** * Checks if the site is a function app. @@ -81,109 +80,83 @@ module Web { } /** - * Returns true if HTTPS-only setting is enabled. + * Gets the extendedLocation configuration. */ - predicate isHttpsOnly() { - exists(BooleanLiteral httpsOnly | - httpsOnly = this.getProperties().getHttpsOnly() and - httpsOnly.getBool() = true - ) - } + ExtendedLocation getExtendedLocation() { result = this.getProperty("extendedLocation") } /** - * Gets the extendedLocation configuration. + * Gets the virtualNetworkSubnetId property. */ - Object getExtendedLocation() { result = this.getProperty("extendedLocation") } + String getVirtualNetworkSubnetId() { result = this.getProperties().getVirtualNetworkSubnetId() } /** * Gets the clientAffinityEnabled flag. */ - BooleanLiteral getClientAffinityEnabled() { result = this.getProperty("clientAffinityEnabled") } + Boolean getClientAffinityEnabled() { result = this.getProperties().getClientAffinityEnabled() } /** - * Returns true if client affinity is enabled. + * Gets the clientCertEnabled flag. */ - predicate isClientAffinityEnabled() { - exists(BooleanLiteral clientAffinity | - clientAffinity = this.getClientAffinityEnabled() and - clientAffinity.getBool() = true - ) - } + Boolean getClientCertEnabled() { result = this.getProperties().getClientCertEnabled() } /** - * Gets the clientCertEnabled flag. + * Returns true if the site has client certificates enabled. */ - BooleanLiteral getClientCertEnabled() { result = this.getProperty("clientCertEnabled") } + predicate isClientCertEnabled() { this.getProperties().isClientCertEnabled() } /** - * Returns true if client certificates are enabled. + * Gets the hostNameSslStates array. */ - predicate isClientCertEnabled() { - exists(BooleanLiteral clientCert | - clientCert = this.getClientCertEnabled() and - clientCert.getBool() = true - ) - } + Array getHostNameSslStates() { result = this.getProperties().getHostNameSslStates() } /** - * Gets the clientCertMode setting. + * Gets the hyperV setting. */ - StringLiteral getClientCertMode() { result = this.getProperty("clientCertMode") } + Boolean getHyperV() { result = this.getProperties().getHyperV() } /** - * Gets the hostNameSslStates array. + * Gets the keyVaultReferenceIdentity. */ - Array getHostNameSslStates() { result = this.getProperty("hostNameSslStates") } + String getKeyVaultReferenceIdentity() { result = this.getProperties().getKeyVaultReferenceIdentity() } /** - * Gets the hyperV setting. + * Gets the redundancyMode. */ - BooleanLiteral getHyperV() { result = this.getProperty("hyperV") } + String getRedundancyMode() { result = this.getProperties().getRedundancyMode() } /** - * Returns true if Hyper-V is enabled. + * Gets the storageAccountRequired flag. */ - predicate isHyperVEnabled() { - exists(BooleanLiteral hyperv | - hyperv = this.getHyperV() and - hyperv.getBool() = true - ) - } + Boolean getStorageAccountRequired() { result = this.getProperties().getStorageAccountRequired() } /** - * Gets the keyVaultReferenceIdentity. + * Returns true if client affinity is enabled. */ - StringLiteral getKeyVaultReferenceIdentity() { - result = this.getProperty("keyVaultReferenceIdentity") - } + predicate isClientAffinityEnabled() { this.getProperties().isClientAffinityEnabled() } /** - * Gets the redundancyMode. + * Returns true if Hyper-V is enabled. */ - StringLiteral getRedundancyMode() { result = this.getProperty("redundancyMode") } + predicate isHyperVEnabled() { this.getProperties().isHyperVEnabled() } /** - * Gets the storageAccountRequired flag. + * Returns true if a storage account is required. */ - BooleanLiteral getStorageAccountRequired() { - result = this.getProperty("storageAccountRequired") - } + predicate isStorageAccountRequired() { this.getProperties().isStorageAccountRequired() } /** - * Returns true if a storage account is required. + * Gets the httpsOnly flag for the site. */ - predicate isStorageAccountRequired() { - exists(BooleanLiteral storageReq | - storageReq = this.getStorageAccountRequired() and - storageReq.getBool() = true - ) - } + Boolean getHttpsOnly() { result = this.getProperties().getHttpsOnly() } /** - * Gets the virtualNetworkSubnetId. + * Returns true if the site has HTTPS-only mode enabled. */ - StringLiteral getVirtualNetworkSubnetId() { - result = this.getProperty("virtualNetworkSubnetId") + predicate isHttpsOnly() { + exists(Boolean httpsOnly | + httpsOnly = this.getHttpsOnly() and + httpsOnly.getBool() = true + ) } override string toString() { result = "AppService[" + this.getIdentifier().getName() + "]" } @@ -212,36 +185,33 @@ module Web { /** * Gets whether the App Service Plan is reserved (for Linux). */ - BooleanLiteral getReserved() { result = this.getProperty("reserved") } + Boolean getReserved() { result = this.getProperty("reserved") } /** * Returns true if the App Service Plan is reserved (for Linux). */ predicate isReserved() { - exists(BooleanLiteral reserved | + exists(Boolean reserved | reserved = this.getReserved() and reserved.getBool() = true ) } /** - * Gets the hosting environment profile. + * Gets whether zone redundant deployment is enabled. */ - Object getHostingEnvironmentProfile() { result = this.getProperty("hostingEnvironmentProfile") } - + Boolean getZoneRedundant() { result = this.getProperties().getZoneRedundant() } + /** - * Gets whether zone redundancy is enabled. + * Returns true if zone redundant deployment is enabled. */ - BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + predicate isZoneRedundant() { this.getProperties().isZoneRedundant() } /** - * Returns true if zone redundancy is enabled. + * Gets the hosting environment profile. */ - predicate isZoneRedundant() { - exists(BooleanLiteral zoneRedundant | - zoneRedundant = this.getZoneRedundant() and - zoneRedundant.getBool() = true - ) + HostingEnvironmentProfile getHostingEnvironmentProfile() { + result = this.getProperties().getHostingEnvironmentProfile() } override string toString() { result = "AppServicePlan[" + this.getIdentifier().getName() + "]" } @@ -260,7 +230,7 @@ module Web { /** * Gets the properties object for the deployment slot. */ - SitesProperties::Properties getProperties() { result = this.getProperty("properties") } + SlotProperties::Properties getProperties() { result = this.getProperty("properties") } /** * Gets the identity configuration for the deployment slot. @@ -270,28 +240,13 @@ module Web { /** * Gets the kind of the deployment slot. */ - StringLiteral getKind() { result = this.getProperty("kind") } + String getKind() { result = this.getProperty("kind") } /** * Returns the kind of the deployment slot as a string. */ string kind() { result = this.getKind().getValue() } - /** - * Gets the HTTPS-only flag for the deployment slot. - */ - BooleanLiteral getHttpsOnly() { result = this.getProperty("httpsOnly") } - - /** - * Returns true if HTTPS-only setting is enabled for the deployment slot. - */ - predicate isHttpsOnly() { - exists(BooleanLiteral httpsOnly | - httpsOnly = this.getHttpsOnly() and - httpsOnly.getBool() = true - ) - } - /** * Gets the parent site name. */ @@ -314,6 +269,16 @@ module Web { ) } + /** + * Gets the HTTPS-only flag for the deployment slot. + */ + Boolean getHttpsOnly() { result = this.getProperties().getHttpsOnly() } + + /** + * Returns true if HTTPS-only setting is enabled for the deployment slot. + */ + predicate isHttpsOnly() { this.getProperties().isHttpsOnly() } + override string toString() { result = "DeploymentSlot[" + this.getIdentifier().getName() + "]" } } @@ -337,11 +302,6 @@ module Web { */ SitesProperties::SiteIdentity getIdentity() { result = this.getProperty("identity") } - /** - * Gets the SKU object for the Static Web App. - */ - override Sku getSku() { result = this.getProperty("sku") } - override string toString() { result = "StaticWebApp[" + this.getIdentifier().getName() + "]" } } @@ -367,7 +327,7 @@ module Web { /** * Gets the kind of the App Service Environment. */ - StringLiteral getKind() { result = this.getProperty("kind") } + String getKind() { result = this.getProperty("kind") } /** * Returns the kind of the App Service Environment as a string. @@ -379,6 +339,156 @@ module Web { } } + /** + * Represents an extended location configuration. + */ + class ExtendedLocation extends Object { + private WebResource parent; + + /** + * Constructs an ExtendedLocation object. + */ + ExtendedLocation() { + this = parent.getProperty("extendedLocation") + } + + /** + * Gets the name of the extended location. + */ + String getName() { result = this.getProperty("name") } + + /** + * Gets the type of the extended location. + */ + String getType() { result = this.getProperty("type") } + + string toString() { result = "ExtendedLocation" } + } + + /** + * Represents a hosting environment profile configuration. + */ + class HostingEnvironmentProfile extends Object { + /** + * Constructs a HostingEnvironmentProfile object. + */ + HostingEnvironmentProfile() { + // This object can be referenced from multiple parent types + exists(WebResource resource | this = resource.getProperty("hostingEnvironmentProfile")) or + exists(SitesProperties::Properties props | this = props.getProperty("hostingEnvironmentProfile")) or + exists(SlotProperties::Properties props | this = props.getProperty("hostingEnvironmentProfile")) or + exists(ServerFarmsProperties::Properties props | this = props.getProperty("hostingEnvironmentProfile")) + } + + /** + * Gets the ID of the hosting environment. + */ + String getId() { result = this.getProperty("id") } + + /** + * Gets the name of the hosting environment. + */ + String getName() { result = this.getProperty("name") } + + string toString() { result = "HostingEnvironmentProfile" } + } + + /** + * Represents an application stack configuration. + */ + class ApplicationStack extends Object { + private SitesProperties::SiteConfig parent; + + /** + * Constructs an ApplicationStack object. + */ + ApplicationStack() { + this = parent.getProperty("applicationStack") + } + + string toString() { result = "ApplicationStack" } + } + + /** + * Represents app settings configuration. + */ + class AppSettings extends Object { + private SitesProperties::SiteConfig parent; + + /** + * Constructs an AppSettings object. + */ + AppSettings() { + this = parent.getProperty("appSettings") + } + + string toString() { result = "AppSettings" } + } + + /** + * Represents user-assigned identities configuration. + */ + class UserAssignedIdentities extends Object { + private SitesProperties::SiteIdentity parent; + + /** + * Constructs a UserAssignedIdentities object. + */ + UserAssignedIdentities() { + this = parent.getProperty("userAssignedIdentities") + } + + string toString() { result = "UserAssignedIdentities" } + } + + /** + * Represents repository branch configuration. + */ + class RepositoryBranch extends Object { + private StaticSitesProperties::Properties parent; + + /** + * Constructs a RepositoryBranch object. + */ + RepositoryBranch() { + this = parent.getProperty("repositoryBranch") + } + + /** + * Gets the name of the branch. + */ + String getName() { result = this.getProperty("name") } + + string toString() { result = "RepositoryBranch" } + } + + /** + * Represents an HTTPS certificate configuration. + */ + class HttpsCertificate extends Object { + private SitesProperties::HttpsCertificates parent; + private int index; + + /** + * Constructs an HttpsCertificate object. + */ + HttpsCertificate() { + this = parent.getElement(index) + } + + /** + * Gets the name of the certificate. + */ + String getName() { result = this.getProperty("name") } + + /** + * Gets the thumbprint of the certificate. + */ + String getThumbprint() { result = this.getProperty("thumbprint") } + + string toString() { result = "HttpsCertificate" } + } + /** * Module containing properties and configurations for Microsoft.Web/sites resources. */ @@ -387,12 +497,12 @@ module Web { * Represents the properties object for a Microsoft.Web/sites resource. */ class Properties extends ResourceProperties { - private SitesResource site; + private SitesResource parent; /** * Constructs a Properties object for the given site. */ - Properties() { this = site.getProperty("properties") } + Properties() { this = parent.getProperty("properties") } /** * Gets the site configuration. @@ -402,42 +512,43 @@ module Web { /** * Gets the serverFarmId (App Service Plan ID). */ - StringLiteral getServerFarmId() { result = this.getProperty("serverFarmId") } + String getServerFarmId() { result = this.getProperty("serverFarmId") } /** * Gets the hostingEnvironmentProfile. */ - Object getHostingEnvironmentProfile() { + HostingEnvironmentProfile getHostingEnvironmentProfile() { result = this.getProperty("hostingEnvironmentProfile") } /** * Gets the public network access setting. */ - StringLiteral getPublicNetworkAccess() { result = this.getProperty("publicNetworkAccess") } + String getPublicNetworkAccess() { result = this.getProperty("publicNetworkAccess") } /** * Gets the HTTPS-only flag for the App Service. */ - BooleanLiteral getHttpsOnly() { result = this.getProperty("httpsOnly") } + Boolean getHttpsOnly() { result = this.getProperty("httpsOnly") } /** * Gets the virtualNetworkSubnetId. */ - StringLiteral getVirtualNetworkSubnetId() { - result = this.getProperty("virtualNetworkSubnetId") + String getVirtualNetworkSubnetId() { + result = this.getProperty("virtualNetworkSubnetId") or + result = parent.getProperty("virtualNetworkSubnetId") } /** * Gets the enabled value. */ - BooleanLiteral getEnabled() { result = this.getProperty("enabled") } + Boolean getEnabled() { result = this.getProperty("enabled") } /** * Returns true if the site is enabled. */ predicate isEnabled() { - exists(BooleanLiteral enabled | + exists(Boolean enabled | enabled = this.getEnabled() and enabled.getBool() = true ) @@ -446,13 +557,13 @@ module Web { /** * Gets the client certificate mode. */ - StringLiteral getClientCertMode() { result = this.getProperty("clientCertMode") } + String getClientCertMode() { result = this.getProperty("clientCertMode") } /** * Returns true if client certificate is required. */ predicate isClientCertRequired() { - exists(StringLiteral mode | + exists(String mode | mode = this.getClientCertMode() and mode.getValue() = "Required" ) @@ -461,9 +572,7 @@ module Web { /** * Gets the client certificate exclusion paths. */ - StringLiteral getClientCertExclusionPaths() { - result = this.getProperty("clientCertExclusionPaths") - } + String getClientCertExclusionPaths() { result = this.getProperty("clientCertExclusionPaths") } /** * Gets the container size. @@ -473,7 +582,7 @@ module Web { /** * Gets the custom domain verification ID. */ - StringLiteral getCustomDomainVerificationId() { + String getCustomDomainVerificationId() { result = this.getProperty("customDomainVerificationId") } @@ -485,18 +594,114 @@ module Web { /** * Gets the default hostname. */ - StringLiteral getDefaultHostname() { result = this.getProperty("defaultHostname") } + String getDefaultHostname() { result = this.getProperty("defaultHostname") } /** * Gets the https certificate settings. */ HttpsCertificates getHttpsCertificates() { result = this.getProperty("httpsCertificates") } + + /** + * Gets the clientAffinityEnabled flag. + */ + Boolean getClientAffinityEnabled() { + result = this.getProperty("clientAffinityEnabled") or + result = parent.getProperty("clientAffinityEnabled") + } + + /** + * Returns true if client affinity is enabled. + */ + predicate isClientAffinityEnabled() { + exists(Boolean clientAffinity | + clientAffinity = this.getClientAffinityEnabled() and + clientAffinity.getBool() = true + ) + } + + /** + * Gets the clientCertEnabled flag. + */ + Boolean getClientCertEnabled() { + result = this.getProperty("clientCertEnabled") or + result = parent.getProperty("clientCertEnabled") + } + + /** + * Returns true if client certificates are enabled. + */ + predicate isClientCertEnabled() { + exists(Boolean clientCert | + clientCert = this.getClientCertEnabled() and + clientCert.getBool() = true + ) + } + + /** + * Gets the hostNameSslStates array. + */ + Array getHostNameSslStates() { + result = this.getProperty("hostNameSslStates") or + result = parent.getProperty("hostNameSslStates") + } + + /** + * Gets the hyperV setting. + */ + Boolean getHyperV() { + result = this.getProperty("hyperV") or + result = parent.getProperty("hyperV") + } + + /** + * Returns true if Hyper-V is enabled. + */ + predicate isHyperVEnabled() { + exists(Boolean hyperv | + hyperv = this.getHyperV() and + hyperv.getBool() = true + ) + } + + /** + * Gets the keyVaultReferenceIdentity. + */ + String getKeyVaultReferenceIdentity() { + result = this.getProperty("keyVaultReferenceIdentity") or + result = parent.getProperty("keyVaultReferenceIdentity") + } + + /** + * Gets the redundancyMode. + */ + String getRedundancyMode() { + result = this.getProperty("redundancyMode") or + result = parent.getProperty("redundancyMode") + } + + /** + * Gets the storageAccountRequired flag. + */ + Boolean getStorageAccountRequired() { + result = this.getProperty("storageAccountRequired") or + result = parent.getProperty("storageAccountRequired") + } + + /** + * Returns true if a storage account is required. + */ + predicate isStorageAccountRequired() { + exists(Boolean storageReq | + storageReq = this.getStorageAccountRequired() and + storageReq.getBool() = true + ) + } /** * Returns true if public network access is enabled. */ predicate isPublicNetworkAccessEnabled() { - not exists(StringLiteral publicNetworkAccess | + not exists(String publicNetworkAccess | publicNetworkAccess = this.getPublicNetworkAccess() and publicNetworkAccess.getValue() = "Disabled" ) @@ -509,35 +714,33 @@ module Web { * Represents the site configuration for a Microsoft.Web/sites resource. */ class SiteConfig extends Object { - private Properties properties; + private Properties parent; /** * Constructs a SiteConfig object. */ - SiteConfig() { this = properties.getProperty("siteConfig") } + SiteConfig() { this = parent.getProperty("siteConfig") } /** * Gets the minimum TLS version. */ - StringLiteral getMinTlsVersion() { result = this.getProperty("minTlsVersion") } + String getMinTlsVersion() { result = this.getProperty("minTlsVersion") } /** * Gets the ftps state setting. */ - StringLiteral getFtpsState() { result = this.getProperty("ftpsState") } + String getFtpsState() { result = this.getProperty("ftpsState") } /** * Gets whether remote debugging is enabled. */ - BooleanLiteral getRemoteDebuggingEnabled() { - result = this.getProperty("remoteDebuggingEnabled") - } + Boolean getRemoteDebuggingEnabled() { result = this.getProperty("remoteDebuggingEnabled") } /** * Returns true if remote debugging is enabled. */ predicate isRemoteDebuggingEnabled() { - exists(BooleanLiteral debugEnabled | + exists(Boolean debugEnabled | debugEnabled = this.getRemoteDebuggingEnabled() and debugEnabled.getBool() = true ) @@ -546,20 +749,18 @@ module Web { /** * Gets the remote debugging version. */ - StringLiteral getRemoteDebuggingVersion() { - result = this.getProperty("remoteDebuggingVersion") - } + String getRemoteDebuggingVersion() { result = this.getProperty("remoteDebuggingVersion") } /** * Gets whether HTTP 2.0 is enabled. */ - BooleanLiteral getHttp20Enabled() { result = this.getProperty("http20Enabled") } + Boolean getHttp20Enabled() { result = this.getProperty("http20Enabled") } /** * Returns true if HTTP 2.0 is enabled. */ predicate isHttp20Enabled() { - exists(BooleanLiteral http20 | + exists(Boolean http20 | http20 = this.getHttp20Enabled() and http20.getBool() = true ) @@ -568,7 +769,7 @@ module Web { /** * Gets whether Always On is enabled. */ - BooleanLiteral getAlwaysOn() { result = this.getProperty("alwaysOn") } + Boolean getAlwaysOn() { result = this.getProperty("alwaysOn") } /** * Returns the Always On setting as a boolean. @@ -579,7 +780,7 @@ module Web { * Returns true if Always On is enabled. */ predicate isAlwaysOn() { - exists(BooleanLiteral alwaysOn | + exists(Boolean alwaysOn | alwaysOn = this.getAlwaysOn() and alwaysOn.getBool() = true ) @@ -588,13 +789,13 @@ module Web { /** * Gets whether web sockets are enabled. */ - BooleanLiteral getWebSocketsEnabled() { result = this.getProperty("webSocketsEnabled") } + Boolean getWebSocketsEnabled() { result = this.getProperty("webSocketsEnabled") } /** * Returns true if web sockets are enabled. */ predicate areWebSocketsEnabled() { - exists(BooleanLiteral webSockets | + exists(Boolean webSockets | webSockets = this.getWebSocketsEnabled() and webSockets.getBool() = true ) @@ -603,7 +804,7 @@ module Web { /** * Gets the application stack. */ - Object getApplicationStack() { result = this.getProperty("applicationStack") } + ApplicationStack getApplicationStack() { result = this.getProperty("applicationStack") } /** * Gets the connection strings. @@ -613,7 +814,7 @@ module Web { /** * Gets the app settings. */ - Object getAppSettings() { result = this.getProperty("appSettings") } + AppSettings getAppSettings() { result = this.getProperty("appSettings") } /** * Gets the CORS settings. @@ -623,17 +824,17 @@ module Web { /** * Gets the Linux FX version. */ - StringLiteral getLinuxFxVersion() { result = this.getProperty("linuxFxVersion") } + String getLinuxFxVersion() { result = this.getProperty("linuxFxVersion") } /** * Gets the Windows FX version. */ - StringLiteral getWindowsFxVersion() { result = this.getProperty("windowsFxVersion") } + String getWindowsFxVersion() { result = this.getProperty("windowsFxVersion") } /** * Gets the health check path. */ - StringLiteral getHealthCheckPath() { result = this.getProperty("healthCheckPath") } + String getHealthCheckPath() { result = this.getProperty("healthCheckPath") } string toString() { result = "SiteConfig" } } @@ -642,12 +843,12 @@ module Web { * Represents the CORS settings for a site configuration. */ class CorsSettings extends Object { - private SiteConfig siteConfig; + private SiteConfig parent; /** * Constructs a CorsSettings object. */ - CorsSettings() { this = siteConfig.getProperty("cors") } + CorsSettings() { this = parent.getProperty("cors") } /** * Gets the allowed origins. @@ -657,13 +858,13 @@ module Web { /** * Gets whether credentials are supported. */ - BooleanLiteral getSupportCredentials() { result = this.getProperty("supportCredentials") } + Boolean getSupportCredentials() { result = this.getProperty("supportCredentials") } /** * Returns true if credentials are supported. */ predicate areCredentialsSupported() { - exists(BooleanLiteral credentials | + exists(Boolean credentials | credentials = this.getSupportCredentials() and credentials.getBool() = true ) @@ -676,17 +877,17 @@ module Web { * Represents the HTTPS certificates configuration for a site. */ class HttpsCertificates extends Array { - private Properties properties; + private Properties parent; /** * Constructs an HttpsCertificates object. */ - HttpsCertificates() { this = properties.getProperty("httpsCertificates") } + HttpsCertificates() { this = parent.getProperty("httpsCertificates") } /** * Gets a certificate by index. */ - Object getCertificate(int index) { result = this.getElement(index) } + HttpsCertificate getCertificate(int index) { result = this.getElement(index) } string toString() { result = "HttpsCertificates" } } @@ -695,17 +896,17 @@ module Web { * Represents the identity configuration for a Microsoft.Web/sites resource. */ class SiteIdentity extends Object { - private SitesResource site; + private SitesResource parent; /** * Constructs a SiteIdentity object. */ - SiteIdentity() { this = site.getProperty("identity") } + SiteIdentity() { this = parent.getProperty("identity") } /** * Gets the type of managed identity. */ - StringLiteral getType() { result = this.getProperty("type") } + String getType() { result = this.getProperty("type") } /** * Returns the managed identity type as a string. @@ -731,7 +932,9 @@ module Web { /** * Gets the user-assigned identities. */ - Object getUserAssignedIdentities() { result = this.getProperty("userAssignedIdentities") } + UserAssignedIdentities getUserAssignedIdentities() { + result = this.getProperty("userAssignedIdentities") + } string toString() { result = "SiteIdentity" } } @@ -745,32 +948,28 @@ module Web { * Represents the properties object for a Microsoft.Web/staticSites resource. */ class Properties extends ResourceProperties { - private StaticSitesResource staticSite; + private StaticSitesResource parent; /** * Constructs a Properties object for the given static site. */ - Properties() { this = staticSite.getProperty("properties") } + Properties() { this = parent.getProperty("properties") } /** * Gets the staging environment policy. */ - StringLiteral getStagingEnvironmentPolicy() { - result = this.getProperty("stagingEnvironmentPolicy") - } + String getStagingEnvironmentPolicy() { result = this.getProperty("stagingEnvironmentPolicy") } /** * Gets whether private endpoint connections are allowed. */ - BooleanLiteral getAllowConfigFileUpdates() { - result = this.getProperty("allowConfigFileUpdates") - } + Boolean getAllowConfigFileUpdates() { result = this.getProperty("allowConfigFileUpdates") } /** * Returns true if config file updates are allowed. */ predicate areConfigFileUpdatesAllowed() { - exists(BooleanLiteral allowUpdates | + exists(Boolean allowUpdates | allowUpdates = this.getAllowConfigFileUpdates() and allowUpdates.getBool() = true ) @@ -779,30 +978,28 @@ module Web { /** * Gets the branch configuration for the repository. */ - Object getRepositoryBranch() { result = this.getProperty("repositoryBranch") } + RepositoryBranch getRepositoryBranch() { result = this.getProperty("repositoryBranch") } /** * Gets the repository token for the Static Web App. */ - StringLiteral getRepositoryToken() { result = this.getProperty("repositoryToken") } + String getRepositoryToken() { result = this.getProperty("repositoryToken") } /** * Gets the repository URL for the Static Web App. */ - StringLiteral getRepositoryUrl() { result = this.getProperty("repositoryUrl") } + String getRepositoryUrl() { result = this.getProperty("repositoryUrl") } /** * Gets whether private endpoint connections are allowed. */ - BooleanLiteral getAllowPrivateEndpoints() { - result = this.getProperty("allowPrivateEndpoints") - } + Boolean getAllowPrivateEndpoints() { result = this.getProperty("allowPrivateEndpoints") } /** * Returns true if private endpoints are allowed. */ predicate arePrivateEndpointsAllowed() { - exists(BooleanLiteral allowEndpoints | + exists(Boolean allowEndpoints | allowEndpoints = this.getAllowPrivateEndpoints() and allowEndpoints.getBool() = true ) @@ -820,12 +1017,12 @@ module Web { * Represents the properties object for a Microsoft.Web/hostingEnvironments resource. */ class Properties extends ResourceProperties { - private HostingEnvironmentsResource hostingEnv; + private HostingEnvironmentsResource parent; /** * Constructs a Properties object for the given hosting environment. */ - Properties() { this = hostingEnv.getProperty("properties") } + Properties() { this = parent.getProperty("properties") } /** * Gets the dedicated host count. @@ -835,13 +1032,13 @@ module Web { /** * Gets whether zone redundancy is enabled. */ - BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + Boolean getZoneRedundant() { result = this.getProperty("zoneRedundant") } /** * Returns true if zone redundancy is enabled. */ predicate isZoneRedundant() { - exists(BooleanLiteral zoneRedundant | + exists(Boolean zoneRedundant | zoneRedundant = this.getZoneRedundant() and zoneRedundant.getBool() = true ) @@ -850,7 +1047,7 @@ module Web { /** * Gets the internal load balancing mode. */ - StringLiteral getInternalLoadBalancingMode() { + String getInternalLoadBalancingMode() { result = this.getProperty("internalLoadBalancingMode") } @@ -873,17 +1070,17 @@ module Web { * Represents the virtual network configuration for an App Service Environment. */ class VnetConfiguration extends Object { - private Properties props; + private Properties parent; /** * Constructs a VnetConfiguration object. */ - VnetConfiguration() { this = props.getProperty("virtualNetworkProfile") } + VnetConfiguration() { this = parent.getProperty("virtualNetworkProfile") } /** * Gets the subnet ID. */ - StringLiteral getSubnetId() { result = this.getProperty("id") } + String getSubnetId() { result = this.getProperty("id") } /** * Gets the subnet resource ID. @@ -902,17 +1099,17 @@ module Web { * Represents the properties object for a Microsoft.Web/serverfarms resource. */ class Properties extends ResourceProperties { - private ServerFarmsResource serverFarm; + private ServerFarmsResource parent; /** * Constructs a Properties object for the given server farm. */ - Properties() { this = serverFarm.getProperty("properties") } + Properties() { this = parent.getProperty("properties") } /** * Gets the compute mode. */ - StringLiteral getComputeMode() { result = this.getProperty("computeMode") } + String getComputeMode() { result = this.getProperty("computeMode") } /** * Returns the compute mode as a string. @@ -922,7 +1119,7 @@ module Web { /** * Gets the worker size. */ - StringLiteral getWorkerSize() { result = this.getProperty("workerSize") } + String getWorkerSize() { result = this.getProperty("workerSize") } /** * Returns the worker size as a string. @@ -952,13 +1149,13 @@ module Web { /** * Gets the per site scaling setting. */ - BooleanLiteral getPerSiteScaling() { result = this.getProperty("perSiteScaling") } + Boolean getPerSiteScaling() { result = this.getProperty("perSiteScaling") } /** * Returns true if per-site scaling is enabled. */ predicate isPerSiteScalingEnabled() { - exists(BooleanLiteral perSiteScaling | + exists(Boolean perSiteScaling | perSiteScaling = this.getPerSiteScaling() and perSiteScaling.getBool() = true ) @@ -967,13 +1164,13 @@ module Web { /** * Gets the elastic scaling setting. */ - BooleanLiteral getElasticScaleEnabled() { result = this.getProperty("elasticScaleEnabled") } + Boolean getElasticScaleEnabled() { result = this.getProperty("elasticScaleEnabled") } /** * Returns true if elastic scaling is enabled. */ predicate isElasticScaleEnabled() { - exists(BooleanLiteral elasticScale | + exists(Boolean elasticScale | elasticScale = this.getElasticScaleEnabled() and elasticScale.getBool() = true ) @@ -982,31 +1179,95 @@ module Web { /** * Gets whether zone redundant deployment is enabled. */ - BooleanLiteral getZoneRedundant() { result = this.getProperty("zoneRedundant") } + Boolean getZoneRedundant() { result = this.getProperty("zoneRedundant") } /** * Returns true if zone redundant deployment is enabled. */ predicate isZoneRedundant() { - exists(BooleanLiteral zoneRedundant | + exists(Boolean zoneRedundant | zoneRedundant = this.getZoneRedundant() and zoneRedundant.getBool() = true ) } /** - * Gets the maximum number of workers. + * Gets the hosting environment profile. */ - Number getMaximumElasticWorkerCount() { - result = this.getProperty("maximumElasticWorkerCount") + HostingEnvironmentProfile getHostingEnvironmentProfile() { + result = parent.getProperty("hostingEnvironmentProfile") } + + /** + * Gets whether the App Service Plan is reserved (for Linux). + */ + Boolean getReserved() { result = parent.getProperty("reserved") } /** - * Returns the maximum number of workers as an integer. + * Returns true if the App Service Plan is reserved (for Linux). */ - int maximumElasticWorkerCount() { result = this.getMaximumElasticWorkerCount().getValue() } + predicate isReserved() { + exists(Boolean reserved | + reserved = this.getReserved() and + reserved.getBool() = true + ) + } override string toString() { result = "ServerFarmProperties" } } } + + /** + * Module containing property classes for Deployment Slot resources. + */ + module SlotProperties { + /** + * Represents the properties object for a Microsoft.Web/sites/slots resource. + */ + class Properties extends ResourceProperties { + private SlotResource parent; + + /** + * Constructs a Properties object for the given slot. + */ + Properties() { this = parent.getProperty("properties") } + + /** + * Gets the HTTPS-only flag for the deployment slot. + */ + Boolean getHttpsOnly() { + result = this.getProperty("httpsOnly") or + result = parent.getProperty("httpsOnly") + } + + /** + * Returns true if HTTPS-only setting is enabled for the deployment slot. + */ + predicate isHttpsOnly() { + exists(Boolean httpsOnly | + httpsOnly = this.getHttpsOnly() and + httpsOnly.getBool() = true + ) + } + + /** + * Gets the site configuration. + */ + SitesProperties::SiteConfig getSiteConfig() { result = this.getProperty("siteConfig") } + + /** + * Gets the serverFarmId (App Service Plan ID). + */ + String getServerFarmId() { result = this.getProperty("serverFarmId") } + + /** + * Gets the hostingEnvironmentProfile. + */ + HostingEnvironmentProfile getHostingEnvironmentProfile() { + result = this.getProperty("hostingEnvironmentProfile") + } + + override string toString() { result = "SlotProperties" } + } + } } From 3bcb2c5728bfae1f3ed3578aa89ee1e12425ed4c Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Wed, 9 Jul 2025 11:29:09 +0100 Subject: [PATCH 5/7] fic: Remove unnecessary import and update expected test output for Web App Always On check --- ql/src/reliability/WebAppAlwaysOnDisabled.ql | 1 - .../WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected | 4 ++-- .../WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ql/src/reliability/WebAppAlwaysOnDisabled.ql b/ql/src/reliability/WebAppAlwaysOnDisabled.ql index d94404d..45b5a91 100644 --- a/ql/src/reliability/WebAppAlwaysOnDisabled.ql +++ b/ql/src/reliability/WebAppAlwaysOnDisabled.ql @@ -13,7 +13,6 @@ */ import bicep -import codeql.bicep.frameworks.Microsoft.Web from Web::SitesResource site, Web::SitesProperties::SiteConfig config where diff --git a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected index 44080aa..1a9eee4 100644 --- a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected +++ b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.expected @@ -1,2 +1,2 @@ -| webapp-without-alwayson | Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts. | -| webapp-with-alwayson-disabled | Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts. | +| app.bicep:17:1:27:1 | AppService[webAppWithoutAlwaysOn] | Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts. | +| app.bicep:30:1:40:1 | AppService[webAppWithAlwaysOnDisabled] | Azure Web App doesn't have Always On enabled, which can lead to poor reliability and potential security issues due to cold starts. | diff --git a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref index 06359de..f2862f9 100644 --- a/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref +++ b/ql/test/queries-tests/reliability/WebAppAlwaysOnDisabled/WebAppAlwaysOnDisabled.qlref @@ -1 +1 @@ -security/reliability/WebAppAlwaysOnDisabled.ql +reliability/WebAppAlwaysOnDisabled.ql From 672bf6965d32d99ac3fd132277a394584a2531c3 Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Wed, 9 Jul 2025 11:48:19 +0100 Subject: [PATCH 6/7] fix: Update tests --- .../WebAppMissingClientCert.expected | 1 - .../SitesWithoutHttpsOnly.expected | 2 +- .../CWE-319/SitesWithoutHttpsOnly/app.bicep | 45 +++---------------- 3 files changed, 8 insertions(+), 40 deletions(-) diff --git a/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected index 67e877f..83cb2f4 100644 --- a/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected +++ b/ql/test/queries-tests/security/CWE-295/WebAppMissingClientCert/WebAppMissingClientCert.expected @@ -1,4 +1,3 @@ | app.bicep:18:1:26:1 | AppService[insecureWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | | app.bicep:30:1:39:1 | AppService[partiallySecureWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | | app.bicep:43:1:52:1 | AppService[explicitlyOptionalWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | -| app.bicep:56:1:65:1 | AppService[secureWebApp] | Azure Web App with HTTPS enabled doesn't require client certificates for mutual TLS authentication. | diff --git a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected index c77a388..43ec9f4 100644 --- a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected +++ b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/SitesWithoutHttpsOnly.expected @@ -1,2 +1,2 @@ | app.bicep:17:1:27:1 | AppService[insecureWebApp] | Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections. | -| app.bicep:30:1:37:1 | AppService[explicitlyInsecureWebApp] | Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections. | +| app.bicep:43:1:53:1 | AppService[missingHttpsOnlyWebApp] | Azure Web App is not configured with HTTPS-only mode, potentially allowing insecure HTTP connections. | diff --git a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep index 03a83d5..6cbe696 100644 --- a/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep +++ b/ql/test/queries-tests/security/CWE-319/SitesWithoutHttpsOnly/app.bicep @@ -22,18 +22,8 @@ resource insecureWebApp 'Microsoft.Web/sites@2022-03-01' = { siteConfig: { ftpsState: 'AllAllowed' // Insecure: allows non-secure FTP } + httpsOnly: false // Explicitly insecure: allows HTTP } - // Missing httpsOnly property or set to false -} - -// Insecure: Web App with HTTPS Only explicitly set to false -resource explicitlyInsecureWebApp 'Microsoft.Web/sites@2022-03-01' = { - name: 'explicitly-insecure-webapp' - location: location - properties: { - serverFarmId: appServicePlan.id - } - httpsOnly: false // Explicitly insecure: allows HTTP } // Secure: Web App with HTTPS Only enabled @@ -45,40 +35,19 @@ resource secureWebApp 'Microsoft.Web/sites@2022-03-01' = { siteConfig: { ftpsState: 'FtpsOnly' // Secure: only allows FTPS } + httpsOnly: true // Secure: enforces HTTPS } - httpsOnly: true // Secure: enforces HTTPS } -// Secure: Web App with HTTPS Only, client certs, and VNet integration -resource highlySecureWebApp 'Microsoft.Web/sites@2022-03-01' = { - name: 'highly-secure-webapp' +// Insecure: Web App with missing httpsOnly property +resource missingHttpsOnlyWebApp 'Microsoft.Web/sites@2022-03-01' = { + name: 'missing-httpsonly-webapp' location: location properties: { serverFarmId: appServicePlan.id - virtualNetworkSubnetId: '/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnet}/subnets/{subnet}' - clientCertEnabled: true - clientCertMode: 'Required' - publicNetworkAccess: 'Disabled' siteConfig: { - ftpsState: 'Disabled' - minTlsVersion: '1.2' - remoteDebuggingEnabled: false - alwaysOn: true - } - } - httpsOnly: true -} - -// Insecure: Web App with remote debugging enabled -resource debuggableWebApp 'Microsoft.Web/sites@2022-03-01' = { - name: 'debuggable-webapp' - location: location - properties: { - serverFarmId: appServicePlan.id - siteConfig: { - remoteDebuggingEnabled: true // Insecure: enables remote debugging - remoteDebuggingVersion: 'VS2019' + ftpsState: 'AllAllowed' // Insecure: allows non-secure FTP } + // httpsOnly is not specified - defaults to false in Azure } - httpsOnly: true } From 8410ea9157e392d04e3b4cfe393b3b567b3679ab Mon Sep 17 00:00:00 2001 From: GeekMasher Date: Wed, 9 Jul 2025 11:50:16 +0100 Subject: [PATCH 7/7] feat: Update Web framework --- .../codeql/bicep/frameworks/Microsoft/Web.qll | 94 +++++++++---------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll index db6fe7a..48d835b 100644 --- a/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/Web.qll @@ -117,7 +117,9 @@ module Web { /** * Gets the keyVaultReferenceIdentity. */ - String getKeyVaultReferenceIdentity() { result = this.getProperties().getKeyVaultReferenceIdentity() } + String getKeyVaultReferenceIdentity() { + result = this.getProperties().getKeyVaultReferenceIdentity() + } /** * Gets the redundancyMode. @@ -127,7 +129,9 @@ module Web { /** * Gets the storageAccountRequired flag. */ - Boolean getStorageAccountRequired() { result = this.getProperties().getStorageAccountRequired() } + Boolean getStorageAccountRequired() { + result = this.getProperties().getStorageAccountRequired() + } /** * Returns true if client affinity is enabled. @@ -201,7 +205,7 @@ module Web { * Gets whether zone redundant deployment is enabled. */ Boolean getZoneRedundant() { result = this.getProperties().getZoneRedundant() } - + /** * Returns true if zone redundant deployment is enabled. */ @@ -210,8 +214,8 @@ module Web { /** * Gets the hosting environment profile. */ - HostingEnvironmentProfile getHostingEnvironmentProfile() { - result = this.getProperties().getHostingEnvironmentProfile() + HostingEnvironmentProfile getHostingEnvironmentProfile() { + result = this.getProperties().getHostingEnvironmentProfile() } override string toString() { result = "AppServicePlan[" + this.getIdentifier().getName() + "]" } @@ -273,7 +277,7 @@ module Web { * Gets the HTTPS-only flag for the deployment slot. */ Boolean getHttpsOnly() { result = this.getProperties().getHttpsOnly() } - + /** * Returns true if HTTPS-only setting is enabled for the deployment slot. */ @@ -348,9 +352,7 @@ module Web { /** * Constructs an ExtendedLocation object. */ - ExtendedLocation() { - this = parent.getProperty("extendedLocation") - } + ExtendedLocation() { this = parent.getProperty("extendedLocation") } /** * Gets the name of the extended location. @@ -375,9 +377,15 @@ module Web { HostingEnvironmentProfile() { // This object can be referenced from multiple parent types exists(WebResource resource | this = resource.getProperty("hostingEnvironmentProfile")) or - exists(SitesProperties::Properties props | this = props.getProperty("hostingEnvironmentProfile")) or - exists(SlotProperties::Properties props | this = props.getProperty("hostingEnvironmentProfile")) or - exists(ServerFarmsProperties::Properties props | this = props.getProperty("hostingEnvironmentProfile")) + exists(SitesProperties::Properties props | + this = props.getProperty("hostingEnvironmentProfile") + ) or + exists(SlotProperties::Properties props | + this = props.getProperty("hostingEnvironmentProfile") + ) or + exists(ServerFarmsProperties::Properties props | + this = props.getProperty("hostingEnvironmentProfile") + ) } /** @@ -402,9 +410,7 @@ module Web { /** * Constructs an ApplicationStack object. */ - ApplicationStack() { - this = parent.getProperty("applicationStack") - } + ApplicationStack() { this = parent.getProperty("applicationStack") } string toString() { result = "ApplicationStack" } } @@ -418,9 +424,7 @@ module Web { /** * Constructs an AppSettings object. */ - AppSettings() { - this = parent.getProperty("appSettings") - } + AppSettings() { this = parent.getProperty("appSettings") } string toString() { result = "AppSettings" } } @@ -434,9 +438,7 @@ module Web { /** * Constructs a UserAssignedIdentities object. */ - UserAssignedIdentities() { - this = parent.getProperty("userAssignedIdentities") - } + UserAssignedIdentities() { this = parent.getProperty("userAssignedIdentities") } string toString() { result = "UserAssignedIdentities" } } @@ -450,9 +452,7 @@ module Web { /** * Constructs a RepositoryBranch object. */ - RepositoryBranch() { - this = parent.getProperty("repositoryBranch") - } + RepositoryBranch() { this = parent.getProperty("repositoryBranch") } /** * Gets the name of the branch. @@ -472,9 +472,7 @@ module Web { /** * Constructs an HttpsCertificate object. */ - HttpsCertificate() { - this = parent.getElement(index) - } + HttpsCertificate() { this = parent.getElement(index) } /** * Gets the name of the certificate. @@ -534,9 +532,9 @@ module Web { /** * Gets the virtualNetworkSubnetId. */ - String getVirtualNetworkSubnetId() { + String getVirtualNetworkSubnetId() { result = this.getProperty("virtualNetworkSubnetId") or - result = parent.getProperty("virtualNetworkSubnetId") + result = parent.getProperty("virtualNetworkSubnetId") } /** @@ -600,13 +598,13 @@ module Web { * Gets the https certificate settings. */ HttpsCertificates getHttpsCertificates() { result = this.getProperty("httpsCertificates") } - + /** * Gets the clientAffinityEnabled flag. */ - Boolean getClientAffinityEnabled() { + Boolean getClientAffinityEnabled() { result = this.getProperty("clientAffinityEnabled") or - result = parent.getProperty("clientAffinityEnabled") + result = parent.getProperty("clientAffinityEnabled") } /** @@ -622,9 +620,9 @@ module Web { /** * Gets the clientCertEnabled flag. */ - Boolean getClientCertEnabled() { + Boolean getClientCertEnabled() { result = this.getProperty("clientCertEnabled") or - result = parent.getProperty("clientCertEnabled") + result = parent.getProperty("clientCertEnabled") } /** @@ -640,17 +638,17 @@ module Web { /** * Gets the hostNameSslStates array. */ - Array getHostNameSslStates() { + Array getHostNameSslStates() { result = this.getProperty("hostNameSslStates") or - result = parent.getProperty("hostNameSslStates") + result = parent.getProperty("hostNameSslStates") } /** * Gets the hyperV setting. */ - Boolean getHyperV() { + Boolean getHyperV() { result = this.getProperty("hyperV") or - result = parent.getProperty("hyperV") + result = parent.getProperty("hyperV") } /** @@ -666,25 +664,25 @@ module Web { /** * Gets the keyVaultReferenceIdentity. */ - String getKeyVaultReferenceIdentity() { + String getKeyVaultReferenceIdentity() { result = this.getProperty("keyVaultReferenceIdentity") or - result = parent.getProperty("keyVaultReferenceIdentity") + result = parent.getProperty("keyVaultReferenceIdentity") } /** * Gets the redundancyMode. */ - String getRedundancyMode() { + String getRedundancyMode() { result = this.getProperty("redundancyMode") or - result = parent.getProperty("redundancyMode") + result = parent.getProperty("redundancyMode") } /** * Gets the storageAccountRequired flag. */ - Boolean getStorageAccountRequired() { + Boolean getStorageAccountRequired() { result = this.getProperty("storageAccountRequired") or - result = parent.getProperty("storageAccountRequired") + result = parent.getProperty("storageAccountRequired") } /** @@ -1197,7 +1195,7 @@ module Web { HostingEnvironmentProfile getHostingEnvironmentProfile() { result = parent.getProperty("hostingEnvironmentProfile") } - + /** * Gets whether the App Service Plan is reserved (for Linux). */ @@ -1235,9 +1233,9 @@ module Web { /** * Gets the HTTPS-only flag for the deployment slot. */ - Boolean getHttpsOnly() { + Boolean getHttpsOnly() { result = this.getProperty("httpsOnly") or - result = parent.getProperty("httpsOnly") + result = parent.getProperty("httpsOnly") } /** @@ -1266,7 +1264,7 @@ module Web { HostingEnvironmentProfile getHostingEnvironmentProfile() { result = this.getProperty("hostingEnvironmentProfile") } - + override string toString() { result = "SlotProperties" } } }