Skip to content

Commit 34a0b88

Browse files
authored
Merge pull request #159 from advanced-security/dep-semver
feat(iac): Update Dependencies and add better Semver support
2 parents f754bc9 + 60ead2d commit 34a0b88

File tree

3 files changed

+111
-12
lines changed

3 files changed

+111
-12
lines changed

ql/lib/codeql/iac/Dependencies.qll

+102-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
1+
/**
2+
* Dependencies module for Infrastructure as Code languages.
3+
*/
14
private import codeql.Locations
25
private import codeql.hcl.Terraform
36

47
/**
58
* Dependency for all Infrastructure as Code languages.
69
*/
7-
class Dependency extends Location {
10+
abstract class Dependency extends Location {
11+
/**
12+
* Gets the name of the dependency.
13+
*/
14+
abstract string getName();
15+
/**
16+
* Gets the version of the dependency (in a format that is human-readable).
17+
*/
18+
abstract string getVersion();
19+
/**
20+
* Gets the raw version of the dependency (in a format that is machine-readable).
21+
*/
22+
abstract string getRawVersion();
23+
/**
24+
* Gets the semantic version of the dependency.
25+
*/
26+
abstract SemanticVersion getSemanticVersion();
27+
}
28+
29+
30+
/**
31+
* Dependency for Terraform.
32+
*/
33+
class TerraformDependency extends Dependency {
834
string name;
935
string version;
1036

11-
Dependency() {
37+
TerraformDependency() {
1238
// Terraform Provider
1339
exists(Terraform::Terraform tf, Terraform::RequiredProvider rp | rp = tf.getRequiredProvider() |
1440
this = rp.getLocation() and
@@ -19,21 +45,87 @@ class Dependency extends Location {
1945

2046
override string toString() { result = this.getName() + "@" + this.getVersion() }
2147

48+
override string getName() { result = name }
49+
50+
51+
override string getVersion() { result = this.getSemanticVersion().getPretty() }
52+
53+
54+
override string getRawVersion() { result = version }
55+
56+
override SemanticVersion getSemanticVersion() { result = version }
57+
}
58+
59+
class SemanticVersion extends string {
60+
Dependency dep;
61+
string normalized;
62+
string pretty;
63+
64+
SemanticVersion() {
65+
this = dep.getRawVersion() and
66+
normalized = normalizeSemver(this) and
67+
pretty = prettySemver(this)
68+
}
69+
2270
/**
23-
* Gets the name of the dependency.
71+
* Holds if this version may be before `last`.
2472
*/
25-
string getName() { result = name }
73+
bindingset[last]
74+
predicate maybeBefore(string last) { normalized < normalizeSemver(last) }
2675

2776
/**
28-
* Gets the version of the dependency.
77+
* Holds if this version may be after `first`.
2978
*/
30-
string getVersion() { result = version.replaceAll("=", "") }
79+
bindingset[first]
80+
predicate maybeAfter(string first) { normalizeSemver(first) < normalized }
3181

32-
SemanticVersion getSemanticVersion() { result = this.getVersion() }
82+
/**
83+
* Holds if this version may be between `first` (inclusive) and `last` (exclusive).
84+
*/
85+
bindingset[first, last]
86+
predicate maybeBetween(string first, string last) {
87+
normalizeSemver(first) <= normalized and
88+
normalized < normalizeSemver(last)
89+
}
90+
91+
/**
92+
* Holds if this version is equivalent to `other`.
93+
*/
94+
bindingset[other]
95+
predicate is(string other) { normalized = normalizeSemver(other) }
96+
97+
string getPretty() { result = pretty }
3398
}
3499

35-
class SemanticVersion extends string {
36-
private Dependency dep;
100+
bindingset[str]
101+
private string leftPad(string str) { result = ("000" + str).suffix(str.length()) }
37102

38-
SemanticVersion() { this = dep.getVersion() }
103+
/**
104+
* Normalizes a SemVer string such that the lexicographical ordering
105+
* of two normalized strings is consistent with the SemVer ordering.
106+
*
107+
* Pre-release information and build metadata is not yet supported.
108+
*/
109+
bindingset[orig]
110+
private string normalizeSemver(string orig) {
111+
exists(string pattern, string major, string minor, string patch |
112+
pattern = "(=|~>|^)(\\d+)\\.(\\d+)\\.(\\d+)" and
113+
major = orig.regexpCapture(pattern, 2) and
114+
minor = orig.regexpCapture(pattern, 3) and
115+
patch = orig.regexpCapture(pattern, 4)
116+
|
117+
result = leftPad(major) + "." + leftPad(minor) + "." + leftPad(patch)
118+
)
39119
}
120+
121+
bindingset[orig]
122+
private string prettySemver(string orig) {
123+
exists(string pattern, string major, string minor, string patch |
124+
pattern = "(=|~>|^)(\\d+)\\.(\\d+)\\.(\\d+)" and
125+
major = orig.regexpCapture(pattern, 2) and
126+
minor = orig.regexpCapture(pattern, 3) and
127+
patch = orig.regexpCapture(pattern, 4)
128+
|
129+
result = major + "." + minor + "." + patch
130+
)
131+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
dependencies
12
| providers.tf:5:15:8:5 | hashicorp/[email protected] |
2-
| providers.tf:9:14:9:22 | hashicorp/time@~>0.7.2 |
3-
| providers.tf:10:14:10:22 | hashicorp/random@~>3.3.1 |
3+
| providers.tf:9:14:9:22 | hashicorp/[email protected] |
4+
| providers.tf:10:14:10:22 | hashicorp/[email protected] |
5+
semver
6+
| providers.tf:5:15:8:5 | hashicorp/[email protected] | =3.0.0 |
7+
| providers.tf:9:14:9:22 | hashicorp/[email protected] | ~>0.7.2 |
8+
| providers.tf:10:14:10:22 | hashicorp/[email protected] | ~>3.3.1 |
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
import hcl
22

33
query predicate dependencies(Dependency d) { any() }
4+
5+
query predicate semver(Dependency d, SemanticVersion v) { d.getSemanticVersion() = v }

0 commit comments

Comments
 (0)