Skip to content

Commit c687a1d

Browse files
dilanbhallaCopilot
andcommitted
Expand Azure Pipelines model foundation
Add structural model coverage for Azure Pipelines documents and templates, including parameters, stages, jobs, checkout/template steps, script variants, and repository/pipeline resources. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8eab11c3-3153-4036-9847-28a2f615835f
1 parent fd99a4a commit c687a1d

4 files changed

Lines changed: 379 additions & 14 deletions

File tree

iac/ql/lib/codeql/iac/azure/Pipelines.qll

Lines changed: 300 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,43 @@ private import codeql.iac.YAML
22
private import codeql.files.FileSystem
33

44
module AzurePipelines {
5+
private predicate hasPipelineBaseName(YamlDocument doc) {
6+
doc.getFile().getBaseName() = ["azure-pipelines.yml", "azure-pipelines.yaml"]
7+
}
8+
9+
private predicate hasPipelineShape(YamlMapping doc) {
10+
exists(doc.lookup("steps")) or
11+
exists(doc.lookup("jobs")) or
12+
exists(doc.lookup("stages")) or
13+
exists(doc.lookup("extends"))
14+
}
15+
516
/**
6-
* Azure DevOps Pipeline file.
17+
* Azure DevOps Pipeline file or referenced template.
718
*/
819
class Document extends YamlNode, YamlDocument, YamlMapping {
920
Document() {
10-
// Check the filename
11-
this.getFile().getBaseName() = ["azure-pipelines.yml", "azure-pipelines.yaml"]
21+
this.getFile().getExtension() = ["yml", "yaml"] and
22+
(hasPipelineBaseName(this) or hasPipelineShape(this))
1223
}
1324

1425
override string toString() { result = "Azure DevOps Pipeline" }
1526

27+
/**
28+
* Gets a top-level trigger-like entry.
29+
*/
30+
YamlValue getTrigger(string name) { result = this.lookup(name) }
31+
1632
/**
1733
* Get the pipeline pool.
1834
*/
1935
Pool getPool() { result = this.lookup("pool") }
2036

37+
/**
38+
* Gets the pipeline parameters.
39+
*/
40+
Parameter getParameters() { result = this.lookup("parameters").getAChild() }
41+
2142
/**
2243
* Get the pipeline variables.
2344
*/
@@ -35,7 +56,31 @@ module AzurePipelines {
3556
/**
3657
* Get the pipeline steps.
3758
*/
38-
Step getSteps() { result = this.lookup("steps").getAChild() }
59+
Step getSteps() { result.getEnclosingDocument() = this }
60+
61+
/**
62+
* Gets the pipeline stages.
63+
*/
64+
Stage getStages() { result = this.lookup("stages").getAChild() }
65+
66+
/**
67+
* Gets the pipeline jobs.
68+
*/
69+
Job getJobs() {
70+
result = this.lookup("jobs").getAChild()
71+
or
72+
result = this.getStages().getJobs()
73+
}
74+
75+
/**
76+
* Gets the pipeline repository resources.
77+
*/
78+
RepositoryResource getRepositoryResources() { result.getEnclosingDocument() = this }
79+
80+
/**
81+
* Gets the pipeline resources.
82+
*/
83+
PipelineResource getPipelineResources() { result.getEnclosingDocument() = this }
3984

4085
/**
4186
* Get the pipeline task steps.
@@ -48,15 +93,114 @@ module AzurePipelines {
4893
Script getScriptSteps() { result = this.getSteps().(Script) }
4994
}
5095

96+
/**
97+
* Azure DevOps Pipeline parameter.
98+
*/
99+
class Parameter extends YamlNode, YamlMapping {
100+
Parameter() { exists(Document document | document.lookup("parameters").getChild(_) = this) }
101+
102+
override string toString() { result = "Parameter '" + this.getName() + "'" }
103+
104+
/**
105+
* Gets the parameter name.
106+
*/
107+
string getName() { result = yamlToString(this.lookup("name")) }
108+
109+
/**
110+
* Gets the parameter type.
111+
*/
112+
string getType() { result = yamlToString(this.lookup("type")) }
113+
114+
/**
115+
* Gets the parameter default value.
116+
*/
117+
YamlValue getDefault() { result = this.lookup("default") }
118+
119+
/**
120+
* Gets an allowed value for the parameter.
121+
*/
122+
YamlValue getAllowedValue() { result = this.lookup("values").getAChild() }
123+
}
124+
125+
/**
126+
* Azure DevOps Pipeline stage.
127+
*/
128+
class Stage extends YamlNode, YamlMapping {
129+
Stage() { exists(Document document | document.lookup("stages").getAChildNode() = this) }
130+
131+
override string toString() { result = "Stage '" + this.getName() + "'" }
132+
133+
/**
134+
* Gets the stage name.
135+
*/
136+
string getName() { result = yamlToString(this.lookup("stage")) }
137+
138+
/**
139+
* Gets a job in the stage.
140+
*/
141+
Job getJobs() { result = this.lookup("jobs").getAChild() }
142+
143+
/**
144+
* Gets the stage condition.
145+
*/
146+
YamlValue getCondition() { result = this.lookup("condition") }
147+
}
148+
149+
/**
150+
* Azure DevOps Pipeline job.
151+
*/
152+
class Job extends YamlNode, YamlMapping {
153+
Job() {
154+
exists(Document document | document.lookup("jobs").getAChildNode() = this)
155+
or
156+
exists(Stage stage | stage.lookup("jobs").getAChildNode() = this)
157+
}
158+
159+
override string toString() { result = "Job '" + this.getName() + "'" }
160+
161+
/**
162+
* Gets the job name.
163+
*/
164+
string getName() {
165+
result = yamlToString(this.lookup("job"))
166+
or
167+
result = yamlToString(this.lookup("deployment"))
168+
}
169+
170+
/**
171+
* Gets the job pool.
172+
*/
173+
Pool getPool() { result = this.lookup("pool") }
174+
175+
/**
176+
* Gets a step in the job.
177+
*/
178+
Step getSteps() { result = this.lookup("steps").getAChild() }
179+
180+
/**
181+
* Gets the job condition.
182+
*/
183+
YamlValue getCondition() { result = this.lookup("condition") }
184+
}
185+
186+
/**
187+
* Azure DevOps Pipeline deployment job.
188+
*/
189+
class DeploymentJob extends Job {
190+
DeploymentJob() { exists(this.lookup("deployment")) }
191+
}
192+
51193
/**
52194
* Azure DevOps Pipeline pool.
53195
*
54196
* https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/pool
55197
*/
56198
class Pool extends YamlNode, YamlMapping {
57-
private Document pipeline;
58-
59-
Pool() { pipeline.lookup("pool") = this }
199+
Pool() {
200+
exists(Document document | document.lookup("pool") = this)
201+
or
202+
exists(Job job | job.lookup("pool") = this)
203+
}
60204

61205
/**
62206
* Get the pool name.
@@ -80,9 +224,13 @@ module AzurePipelines {
80224
* https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables
81225
*/
82226
class Variable extends YamlNode, YamlMapping {
83-
private Document document;
84-
85-
Variable() { document.lookup("variables").getChild(_) = this }
227+
Variable() {
228+
exists(Document document | document.lookup("variables").getChild(_) = this)
229+
or
230+
exists(Stage stage | stage.lookup("variables").getChild(_) = this)
231+
or
232+
exists(Job job | job.lookup("variables").getChild(_) = this)
233+
}
86234

87235
override string toString() { result = "Variable '" + this.getName() + "'" }
88236

@@ -103,12 +251,23 @@ module AzurePipelines {
103251
* https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/steps
104252
*/
105253
class Step extends YamlNode, YamlMapping {
106-
private Document pipeline;
107-
108-
Step() { pipeline.lookup("steps").getAChildNode() = this }
254+
Step() {
255+
exists(Document document | document.lookup("steps").getAChildNode() = this)
256+
or
257+
exists(Job job | job.lookup("steps").getAChildNode() = this)
258+
}
109259

110260
override string toString() { result = "Azure DevOps Pipeline step" }
111261

262+
/**
263+
* Gets the enclosing Azure DevOps Pipeline document.
264+
*/
265+
Document getEnclosingDocument() {
266+
exists(Document document | document.lookup("steps").getAChildNode() = this | result = document)
267+
or
268+
exists(Document document | this.getFile() = document.getFile() | result = document)
269+
}
270+
112271
/**
113272
* Get the step display name.
114273
*/
@@ -121,6 +280,16 @@ module AzurePipelines {
121280
exists(this.lookup("task")) and result = "task"
122281
or
123282
exists(this.lookup("script")) and result = "script"
283+
or
284+
exists(this.lookup("bash")) and result = "bash"
285+
or
286+
exists(this.lookup("powershell")) and result = "powershell"
287+
or
288+
exists(this.lookup("pwsh")) and result = "pwsh"
289+
or
290+
exists(this.lookup("checkout")) and result = "checkout"
291+
or
292+
exists(this.lookup("template")) and result = "template"
124293
}
125294
}
126295

@@ -148,6 +317,123 @@ module AzurePipelines {
148317
* Azure DevOps Pipeline script step.
149318
*/
150319
class Script extends Step {
151-
Script() { this.getType() = "script" }
320+
Script() { this.getType() = ["script", "bash", "powershell", "pwsh"] }
321+
322+
/**
323+
* Gets the script step kind.
324+
*/
325+
string getScriptKind() { result = this.getType() }
326+
327+
/**
328+
* Gets the inline script content.
329+
*/
330+
YamlValue getScriptContent() { result = this.lookup(this.getScriptKind()) }
331+
}
332+
333+
/**
334+
* Azure DevOps Pipeline checkout step.
335+
*/
336+
class Checkout extends Step {
337+
Checkout() { this.getType() = "checkout" }
338+
339+
/**
340+
* Gets the checkout target.
341+
*/
342+
string getRepository() { result = yamlToString(this.lookup("checkout")) }
343+
344+
/**
345+
* Gets the persistCredentials setting.
346+
*/
347+
YamlValue getPersistCredentials() { result = this.lookup("persistCredentials") }
348+
}
349+
350+
/**
351+
* Azure DevOps Pipeline template step.
352+
*/
353+
class TemplateStep extends Step {
354+
TemplateStep() { this.getType() = "template" }
355+
356+
/**
357+
* Gets the referenced template path.
358+
*/
359+
string getTemplate() { result = yamlToString(this.lookup("template")) }
360+
}
361+
362+
/**
363+
* Azure DevOps repository resource.
364+
*/
365+
class RepositoryResource extends YamlNode, YamlMapping {
366+
RepositoryResource() {
367+
exists(Document document |
368+
document.lookup("resources").(YamlMapping).lookup("repositories").getAChildNode() = this
369+
)
370+
}
371+
372+
override string toString() { result = "Repository resource '" + this.getAlias() + "'" }
373+
374+
/**
375+
* Gets the enclosing Azure DevOps Pipeline document.
376+
*/
377+
Document getEnclosingDocument() {
378+
exists(Document document |
379+
document.lookup("resources").(YamlMapping).lookup("repositories").getAChildNode() = this
380+
|
381+
result = document
382+
)
383+
}
384+
385+
/**
386+
* Gets the resource alias.
387+
*/
388+
string getAlias() { result = yamlToString(this.lookup("repository")) }
389+
390+
/**
391+
* Gets the repository name.
392+
*/
393+
string getName() { result = yamlToString(this.lookup("name")) }
394+
395+
/**
396+
* Gets the referenced revision.
397+
*/
398+
string getRef() { result = yamlToString(this.lookup("ref")) }
399+
}
400+
401+
/**
402+
* Azure DevOps pipeline resource.
403+
*/
404+
class PipelineResource extends YamlNode, YamlMapping {
405+
PipelineResource() {
406+
exists(Document document |
407+
document.lookup("resources").(YamlMapping).lookup("pipelines").getAChildNode() = this
408+
)
409+
}
410+
411+
override string toString() { result = "Pipeline resource '" + this.getAlias() + "'" }
412+
413+
/**
414+
* Gets the enclosing Azure DevOps Pipeline document.
415+
*/
416+
Document getEnclosingDocument() {
417+
exists(Document document |
418+
document.lookup("resources").(YamlMapping).lookup("pipelines").getAChildNode() = this
419+
|
420+
result = document
421+
)
422+
}
423+
424+
/**
425+
* Gets the resource alias.
426+
*/
427+
string getAlias() { result = yamlToString(this.lookup("pipeline")) }
428+
429+
/**
430+
* Gets the source pipeline.
431+
*/
432+
string getSource() { result = yamlToString(this.lookup("source")) }
433+
434+
/**
435+
* Gets the branch selector.
436+
*/
437+
string getBranch() { result = yamlToString(this.lookup("branch")) }
152438
}
153439
}

0 commit comments

Comments
 (0)