Skip to content

Commit 32daba3

Browse files
authored
Merge pull request #394 from microsoft/iac-p0/yaml-document-foundation
Add reusable YAML document classification foundation
2 parents 1211b9d + 6f9cd09 commit 32daba3

11 files changed

Lines changed: 166 additions & 0 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Provides YAML document classification shared across IaC YAML query families.
3+
*
4+
* This library identifies the broad document kind of a YAML document (Azure
5+
* DevOps Pipelines, Kubernetes/Helm, Compose, OpenAPI, or CloudFormation) and
6+
* excludes content that is out of scope for YAML-focused IaC queries even
7+
* though it may be reachable through the same YAML abstract syntax tree:
8+
*
9+
* - JSON files. The extractor represents JSON using the same node types as
10+
* YAML, since JSON is a syntactic subset of YAML. Callers that only intend
11+
* to analyze literal YAML syntax must not rely on node type alone.
12+
* - Azure Resource Manager (ARM) templates. ARM templates are JSON by
13+
* default (`azuredeploy.json`), but can also carry a `.yaml` extension
14+
* while keeping the ARM `$schema` marker, so the exclusion is schema-based
15+
* rather than purely extension-based.
16+
*
17+
* Terraform, HCL, and Bicep are not represented as `YamlNode`s at all in this
18+
* extractor, so no additional exclusion is required for them here.
19+
*/
20+
21+
import iac
22+
private import codeql.iac.YAML
23+
private import codeql.iac.azure.Pipelines
24+
private import codeql.iac.helmcharts.HelmChart
25+
private import codeql.iac.compose.Compose
26+
private import codeql.iac.openapi.OpenApi
27+
private import codeql.iac.aws.CloudFormation
28+
29+
module YamlDocumentClassification {
30+
/**
31+
* A YAML document whose source file uses a YAML extension (`.yml` or
32+
* `.yaml`), as opposed to a JSON file that happens to be representable by
33+
* the same node types.
34+
*/
35+
class YamlSyntaxDocument extends YamlNode, YamlDocument, YamlMapping {
36+
YamlSyntaxDocument() { this.getFile().getExtension() = ["yml", "yaml"] }
37+
}
38+
39+
/**
40+
* Holds if `doc` carries the Azure Resource Manager (ARM) template schema
41+
* marker. ARM templates are out of scope for YAML-focused IaC queries even
42+
* when authored with a `.yaml` extension.
43+
*/
44+
private predicate hasArmSchemaMarker(YamlSyntaxDocument doc) {
45+
yamlToString(doc.lookup("$schema")).matches("%schema.management.azure.com%")
46+
}
47+
48+
/**
49+
* The kind of a supported in-scope YAML document.
50+
*/
51+
class DocumentKind extends string {
52+
DocumentKind() {
53+
this = ["ado-pipeline", "kubernetes-helm", "compose", "openapi", "cloudformation"]
54+
}
55+
}
56+
57+
/**
58+
* Holds if `doc` is a supported, in-scope YAML document of the given
59+
* `kind`.
60+
*
61+
* A document is only classified once its file extension is `.yml`/`.yaml`
62+
* and it does not carry the ARM template schema marker. Callers writing
63+
* YAML-only IaC queries should use this predicate, rather than the
64+
* underlying per-schema `Document` classes directly, so that ARM/JSON
65+
* content is consistently excluded.
66+
*/
67+
predicate isSupportedYamlDocument(YamlSyntaxDocument doc, DocumentKind kind) {
68+
not hasArmSchemaMarker(doc) and
69+
(
70+
doc instanceof AzurePipelines::Document and kind = "ado-pipeline"
71+
or
72+
doc instanceof HelmChart::Document and kind = "kubernetes-helm"
73+
or
74+
doc instanceof Compose::Document and kind = "compose"
75+
or
76+
doc instanceof OpenApi::Document and kind = "openapi"
77+
or
78+
doc instanceof CloudFormation::Document and kind = "cloudformation"
79+
)
80+
}
81+
82+
/**
83+
* Gets a supported, in-scope YAML document of any kind.
84+
*/
85+
YamlSyntaxDocument getASupportedYamlDocument() { isSupportedYamlDocument(result, _) }
86+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
supportedYamlDocument
2+
| azure-pipelines.yaml:1:1:8:25 | Azure DevOps Pipeline | ado-pipeline |
3+
| cloudformation.yaml:1:1:6:29 | CloudFormation Document | cloudformation |
4+
| compose.yaml:1:1:4:24 | version: "3.9" | compose |
5+
| deployment.yaml:1:1:8:29 | HelmChart Document | kubernetes-helm |
6+
| openapi.yaml:1:1:12:26 | OpenApi Document | openapi |
7+
allYamlDocuments
8+
| arm-template.yaml:1:1:5:24 | $schema ... .json#" |
9+
| azure-pipelines.yaml:1:1:8:25 | Azure DevOps Pipeline |
10+
| cloudformation.yaml:1:1:6:29 | CloudFormation Document |
11+
| compose.yaml:1:1:4:24 | version: "3.9" |
12+
| deployment.yaml:1:1:8:29 | HelmChart Document |
13+
| openapi.json:1:1:8:1 | OpenApi Document |
14+
| openapi.yaml:1:1:12:26 | OpenApi Document |
15+
| unrelated.yaml:1:1:4:8 | descrip ... ocument |
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
private import iac
2+
private import codeql.iac.YamlDocumentClassification
3+
4+
query predicate supportedYamlDocument(
5+
YamlDocumentClassification::YamlSyntaxDocument doc, YamlDocumentClassification::DocumentKind kind
6+
) {
7+
YamlDocumentClassification::isSupportedYamlDocument(doc, kind)
8+
}
9+
10+
query predicate allYamlDocuments(YamlDocument doc) { any() }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
$schema: "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"
2+
contentVersion: "1.0.0.0"
3+
resources:
4+
- type: Microsoft.Storage/storageAccounts
5+
name: samplestorage
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
trigger:
2+
- main
3+
4+
pool:
5+
vmImage: ubuntu-latest
6+
7+
steps:
8+
- script: echo "hello"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
AWSTemplateFormatVersion: "2010-09-09"
2+
Resources:
3+
Bucket:
4+
Type: AWS::S3::Bucket
5+
Properties:
6+
AccessControl: Private
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
version: "3.9"
2+
services:
3+
web:
4+
image: nginx:latest
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: sample
5+
spec:
6+
containers:
7+
- name: app
8+
image: example/app:1.0
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"openapi": "3.0.0",
3+
"info": {
4+
"title": "Sample API",
5+
"version": "1.0"
6+
},
7+
"paths": {}
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
openapi: "3.0.0"
2+
info:
3+
title: Sample API
4+
version: "1.0"
5+
servers:
6+
- url: https://example.com
7+
paths:
8+
/items:
9+
get:
10+
responses:
11+
"200":
12+
description: OK

0 commit comments

Comments
 (0)