@@ -2,22 +2,43 @@ private import codeql.iac.YAML
22private import codeql.files.FileSystem
33
44module 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