diff --git a/ql/lib/codeql/bicep/Frameworks.qll b/ql/lib/codeql/bicep/Frameworks.qll index 932d795..9f3b65b 100644 --- a/ql/lib/codeql/bicep/Frameworks.qll +++ b/ql/lib/codeql/bicep/Frameworks.qll @@ -1,5 +1,6 @@ import frameworks.Microsoft.Cache import frameworks.Microsoft.Compute +import frameworks.Microsoft.Containers import frameworks.Microsoft.General import frameworks.Microsoft.Network import frameworks.Microsoft.Storage diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/Containers.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/Containers.qll new file mode 100644 index 0000000..3c89e35 --- /dev/null +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/Containers.qll @@ -0,0 +1,296 @@ +private import bicep + +module Containers { + /** + * Represents a Microsoft.ContainerApp/containerApps resource. + * See: https://learn.microsoft.com/en-us/azure/templates/microsoft.app/containerapps + */ + class ContainerResource extends Resource { + /** + * Constructs a ContainerResource for Microsoft.App/containerApps resources. + */ + ContainerResource() { this.getResourceType().regexpMatch("^Microsoft.App/containerApps@.*") } + + /** + * Returns the properties object for the container app resource. + */ + ContainerProperties::Properties getProperties() { result = this.getProperty("properties") } + + /** + * Returns the configuration object for the container app resource. + */ + ContainerProperties::ContainerConfiguration getConfiguration() { + result = this.getProperties().getConfiguration() + } + + /** + * Returns the template object for the container app resource. + */ + ContainerProperties::ContainerTemplate getTemplate() { + result = this.getProperties().getTemplate() + } + + /** + * Returns the containers defined in the template. + */ + ContainerProperties::ContainerApp getContainers() { + result = this.getTemplate().getContainers() + } + + /** + * Returns a specific container by index from the template. + */ + ContainerProperties::ContainerApp getContainer(int index) { + result = this.getTemplate().getContainer(index) + } + + Network::Ingress getNetworkIngress() { + result = this.getConfiguration().getNetworkIngress() + } + + Network::CorsPolicy getCorsPolicy() { + result = this.getNetworkIngress().getCorsPolicy() + } + + /** + * Returns a string representation of the container app resource. + */ + override string toString() { result = "ContainerResource" } + } + + module ContainerProperties { + /** + * Represents the properties object for a container app resource. + */ + class Properties extends Object { + private ContainerResource containerResource; + + /** + * Constructs a Properties object for the given container app resource. + */ + Properties() { this = containerResource.getProperty("properties") } + + /** + * Returns the parent ContainerResource. + */ + ContainerResource getContainerResource() { result = containerResource } + + /** + * Returns the configuration property. + */ + ContainerConfiguration getConfiguration() { result = this.getProperty("configuration") } + + /** + * Returns the template property. + */ + ContainerTemplate getTemplate() { result = this.getProperty("template") } + + string toString() { result = "ContainerProperties" } + } + + /** + * Represents the configuration object for a container app resource. + */ + class ContainerConfiguration extends Object { + private Properties properties; + + /** + * Constructs a Configuration object for the given properties. + */ + ContainerConfiguration() { this = properties.getProperty("configuration") } + + /** + * Returns the network ingress configuration. + */ + Network::Ingress getNetworkIngress() { result = this.getProperty("ingress") } + + /** + * Returns the secrets defined in the configuration. + */ + ContainerSecret getSecrets() { result = this.getProperty("secrets").(Array).getElements() } + + /** + * Returns the active revisions mode as a StringLiteral. + */ + StringLiteral getActiveRevisionsMode() { result = this.getProperty("activeRevisionsMode") } + + /** + * Returns the active revisions mode as a string. + */ + string activeRevisionsMode() { result = this.getActiveRevisionsMode().getValue() } + + /** + * Returns the template property. + */ + Expr getTemplate() { result = this.getProperty("template") } + + string toString() { result = "ContainerConfiguration" } + } + + /** + * Represents a secret defined in the container app configuration. + */ + class ContainerSecret extends Object { + private ContainerConfiguration configuration; + + /** + * Constructs a ContainerSecret for the given configuration. + */ + ContainerSecret() { this = configuration.getProperty("secrets").(Array).getElements() } + + /** + * Returns the name of the secret. + */ + StringLiteral getName() { result = this.getProperty("name") } + + /** + * Returns the value of the secret. + */ + StringLiteral getValue() { result = this.getProperty("value") } + + string toString() { result = "ContainerSecret" } + } + + /** + * Represents the template object for a container app resource. + */ + class ContainerTemplate extends Object { + private Properties properties; + + /** + * Constructs a ContainerTemplate for the given properties. + */ + ContainerTemplate() { this = properties.getProperty("template") } + + /** + * Returns the container app template. + */ + Expr getContainerAppTemplate() { result = this.getProperty("containerAppTemplate") } + + /** + * Returns the containers defined in the template. + */ + ContainerApp getContainers() { result = this.getProperty("containers").(Array).getElements() } + + /** + * Returns a specific container by index from the template. + */ + ContainerApp getContainer(int index) { + result = this.getProperty("containers").(Array).getElement(index) + } + + string toString() { result = "ContainerTemplate" } + } + + /** + * Represents a container defined in the container app template. + */ + class ContainerApp extends Object { + private ContainerTemplate template; + + /** + * Constructs a ContainerApp for the given template. + */ + ContainerApp() { this = template.getProperty("containers").(Array).getElements() } + + /** + * Returns the parent ContainerTemplate. + */ + ContainerTemplate getContainerTemplate() { result = template } + + /** + * Returns the name of the container. + */ + StringLiteral getName() { result = this.getProperty("name") } + + /** + * Returns the image of the container. + */ + StringLiteral getImage() { result = this.getProperty("image") } + + /** + * Returns the resources object for the container. + */ + ContainerResources getResources() { result = this.getProperty("resources") } + + /** + * Returns the environment variables defined for the container. + */ + ContainerEnv getEnvs() { result = this.getProperty("env").(Array).getElements() } + + /** + * Returns a specific environment variable by name. + */ + ContainerEnv getEnv(string name) { + exists(ContainerEnv env | + env = this.getEnvs() and + env.getName().getValue() = name + | + result = env + ) + } + + string toString() { result = "ContainerProperty" } + } + + /** + * Represents the resources object for a container. + */ + class ContainerResources extends Object { + private ContainerApp container; + + /** + * Constructs a ContainerResources object for the given container. + */ + ContainerResources() { this = container.getProperty("resources") } + + /** + * Returns the properties object for the container resource. + */ + ContainerProperties::Properties getContainerProperties() { + result = this.getProperty("properties") + } + + /** + * Returns the CPU resource allocation. + */ + Literals getCpu() { result = this.getProperty("cpu") } + + /** + * Returns the memory resource allocation. + */ + StringLiteral getMemory() { result = this.getProperty("memory") } + + string toString() { result = "ContainerResourceProperties" } + } + + /** + * Represents an environment variable defined for a container. + */ + class ContainerEnv extends Object { + private ContainerApp container; + + /** + * Constructs a ContainerEnv for the given container. + */ + ContainerEnv() { this = container.getProperty("env").(Array).getElements() } + + /** + * Returns the parent ContainerApp. + */ + ContainerApp getContainer() { result = container } + + /** + * Returns the name of the environment variable. + */ + StringLiteral getName() { result = this.getProperty("name") } + + /** + * Returns the value of the environment variable. + */ + StringLiteral getValue() { result = this.getProperty("value") } + + string toString() { result = "ContainerEnv" } + } + } +} diff --git a/ql/lib/codeql/bicep/frameworks/Microsoft/Network.qll b/ql/lib/codeql/bicep/frameworks/Microsoft/Network.qll index 01f1522..85e2d58 100644 --- a/ql/lib/codeql/bicep/frameworks/Microsoft/Network.qll +++ b/ql/lib/codeql/bicep/frameworks/Microsoft/Network.qll @@ -112,7 +112,6 @@ module Network { } } - class NetworkAcl extends Object { private Resource resource; @@ -125,47 +124,149 @@ module Network { Resource getResource() { result = resource } - StringLiteral getBypass() { - result = this.getProperty("bypass") - } + StringLiteral getBypass() { result = this.getProperty("bypass") } - string bypass() { - result = this.getBypass().getValue() - } + string bypass() { result = this.getBypass().getValue() } - StringLiteral getDefaultAction() { - result = this.getProperty("defaultAction") - } + StringLiteral getDefaultAction() { result = this.getProperty("defaultAction") } - string defaultAction() { - result = this.getDefaultAction().getValue() - } + string defaultAction() { result = this.getDefaultAction().getValue() } - IpRule getIpRules() { - result = this.getProperty("ipRules").(Array).getElements() - } + IpRule getIpRules() { result = this.getProperty("ipRules").(Array).getElements() } - string toString() { - result = "Network ACL" - } + string toString() { result = "Network ACL" } } class IpRule extends Object { private NetworkAcl acl; - IpRule() { - this = acl.getProperty("ipRules").(Array).getElements() - } + IpRule() { this = acl.getProperty("ipRules").(Array).getElements() } NetworkAcl getNetworkAcl() { result = acl } - StringLiteral getValue() { - result = this.getProperty("value") + StringLiteral getValue() { result = this.getProperty("value") } + + string toString() { result = "IP Rule" } + } + + /** + * Represents the ingress configuration for a resource (e.g., container app). + * Provides access to ingress properties such as external, targetPort, transport, CORS policy, and allowInsecure. + */ + class Ingress extends Object { + private Object properties; + + /** + * Constructs an Ingress object for the given properties. + */ + Ingress() { this = properties.getProperty("ingress") } + + /** + * Returns the 'external' property as a Boolean. + */ + Boolean getExternal() { result = this.getProperty("external") } + + /** + * Returns the 'external' property as a boolean. + */ + boolean external() { result = this.getExternal().(Boolean).getBool() } + + /** + * Returns the 'targetPort' property as a Number. + */ + Number getTargetPort() { result = this.getProperty("targetPort") } + + /** + * Returns the 'targetPort' property as an int. + */ + int targetPort() { result = this.getTargetPort().getValue() } + + /** + * Returns the 'transport' property as a StringLiteral. + */ + StringLiteral getTransport() { result = this.getProperty("transport") } + + /** + * Returns the 'transport' property as a string. + */ + string transport() { result = this.getTransport().getValue() } + + /** + * Returns the 'corsPolicy' property as a CorsPolicy object. + */ + CorsPolicy getCorsPolicy() { result = this.getProperty("corsPolicy") } + + /** + * Returns the 'allowInsecure' property as a Boolean. + */ + Boolean getAllowInsecure() { result = this.getProperty("allowInsecure") } + + /** + * Returns the 'allowInsecure' property as a boolean. + */ + boolean allowInsecure() { result = this.getAllowInsecure().getBool() } + + string toString() { result = "NetworkIngress" } + } + + /** + * Represents a CORS policy for ingress. + * Provides access to CORS-related properties such as allowCredentials, allowedOrigins, allowedMethods, allowedHeaders, exposedHeaders, and maxAge. + */ + class CorsPolicy extends Object { + private Object properties; + + /** + * Constructs a CorsPolicy object for the given properties. + */ + CorsPolicy() { this = properties.getProperty("corsPolicy") } + + /** + * Returns the 'allowCredentials' property as a Boolean. + */ + Boolean getAllowCredentials() { + result = this.getProperty("allowCredentials") } - string toString() { - result = "IP Rule" + /** + * Returns the 'allowCredentials' property as a boolean. + */ + boolean allowCredentials() { result = this.getAllowCredentials().getBool() } + + /** + * Returns the 'allowedOrigins' property as an array of StringLiterals. + */ + Array getAllowedOrigins() { + result = this.getProperty("allowedOrigins") + } + + /** + * Returns the 'allowedMethods' property as an array of StringLiterals. + */ + Array getAllowedMethods() { + result = this.getProperty("allowedMethods") + } + + /** + * Returns the 'allowedHeaders' property as an array of StringLiterals. + */ + Array getAllowedHeaders() { + result = this.getProperty("allowedHeaders") + } + + /** + * Returns the 'exposedHeaders' property as an array of StringLiterals. + */ + Array getExposedHeaders() { + result = this.getProperty("exposedHeaders") } + + /** + * Returns the 'maxAge' property as a Number. + */ + Number getMaxAge() { result = this.getProperty("maxAge") } + + string toString() { result = "CorsPolicy" } } module VirtualNetworkProperties { diff --git a/ql/test/library-tests/frameworks/containers/Containers.expected b/ql/test/library-tests/frameworks/containers/Containers.expected new file mode 100644 index 0000000..1d65732 --- /dev/null +++ b/ql/test/library-tests/frameworks/containers/Containers.expected @@ -0,0 +1 @@ +| app.bicep:2:1:78:1 | ContainerResource | diff --git a/ql/test/library-tests/frameworks/containers/Containers.ql b/ql/test/library-tests/frameworks/containers/Containers.ql new file mode 100644 index 0000000..2c28362 --- /dev/null +++ b/ql/test/library-tests/frameworks/containers/Containers.ql @@ -0,0 +1,3 @@ +import bicep + +query predicate containers(Containers::ContainerResource container) { any() } diff --git a/ql/test/library-tests/frameworks/containers/app.bicep b/ql/test/library-tests/frameworks/containers/app.bicep new file mode 100644 index 0000000..c7a7e30 --- /dev/null +++ b/ql/test/library-tests/frameworks/containers/app.bicep @@ -0,0 +1,78 @@ +// Example Bicep file for a Container App with various settings +resource myContainerApp 'Microsoft.App/containerApps@2022-03-01' = { + name: 'my-container-app' + location: 'eastus' + properties: { + managedEnvironmentId: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg/providers/Microsoft.App/managedEnvironments/my-env' + configuration: { + ingress: { + external: true + targetPort: 80 + transport: 'auto' + corsPolicy: { + allowCredentials: true + allowedOrigins: [ + 'https://example.com' + 'https://another.com' + ] + allowedMethods: [ + 'GET' + 'POST' + 'OPTIONS' + ] + allowedHeaders: [ + 'Authorization' + 'Content-Type' + ] + exposeHeaders: [ + 'X-Custom-Header' + ] + maxAge: 3600 + } + } + secrets: [ + { + name: 'my-secret' + value: 'supersecretvalue' + } + ] + activeRevisionsMode: 'Multiple' + } + template: { + containers: [ + { + name: 'myapp' + image: 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + resources: { + cpu: 0.5 + memory: '1.0Gi' + } + env: [ + { + name: 'ENV_VAR_1' + value: 'value1' + } + { + name: 'ENV_VAR_2' + secretRef: 'my-secret' + } + ] + } + ] + scale: { + minReplicas: 1 + maxReplicas: 5 + rules: [ + { + name: 'http-scaling' + http: { + metadata: { + concurrentRequests: '50' + } + } + } + ] + } + } + } +}