@ploaness/governance 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/checkov-check.d.ts +9 -0
- package/dist/checkov-check.d.ts.map +1 -0
- package/dist/checkov-check.js +4 -0
- package/dist/checkov-check.js.map +1 -0
- package/dist/checkov-checks-aws.d.ts +4 -0
- package/dist/checkov-checks-aws.d.ts.map +1 -0
- package/dist/checkov-checks-aws.js +93 -0
- package/dist/checkov-checks-aws.js.map +1 -0
- package/dist/checkov-checks-azure.d.ts +4 -0
- package/dist/checkov-checks-azure.d.ts.map +1 -0
- package/dist/checkov-checks-azure.js +32 -0
- package/dist/checkov-checks-azure.js.map +1 -0
- package/dist/checkov-checks-google.d.ts +6 -0
- package/dist/checkov-checks-google.d.ts.map +1 -0
- package/dist/checkov-checks-google.js +53 -0
- package/dist/checkov-checks-google.js.map +1 -0
- package/dist/checkov-policy.d.ts +63 -0
- package/dist/checkov-policy.d.ts.map +1 -0
- package/dist/checkov-policy.js +105 -0
- package/dist/checkov-policy.js.map +1 -0
- package/dist/environment-coherence.d.ts +59 -5
- package/dist/environment-coherence.d.ts.map +1 -1
- package/dist/environment-coherence.js +107 -5
- package/dist/environment-coherence.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/payload-bootstrap.d.ts +52 -0
- package/dist/payload-bootstrap.d.ts.map +1 -0
- package/dist/payload-bootstrap.js +195 -0
- package/dist/payload-bootstrap.js.map +1 -0
- package/dist/payload-database.d.ts +80 -0
- package/dist/payload-database.d.ts.map +1 -0
- package/dist/payload-database.js +164 -0
- package/dist/payload-database.js.map +1 -0
- package/dist/payload-policy.d.ts +8 -0
- package/dist/payload-policy.d.ts.map +1 -1
- package/dist/payload-policy.js +12 -4
- package/dist/payload-policy.js.map +1 -1
- package/dist/shell-files.d.ts +45 -0
- package/dist/shell-files.d.ts.map +1 -0
- package/dist/shell-files.js +105 -0
- package/dist/shell-files.js.map +1 -0
- package/dist/source-security.d.ts +10 -0
- package/dist/source-security.d.ts.map +1 -1
- package/dist/source-security.js +88 -1
- package/dist/source-security.js.map +1 -1
- package/dist/terraform-files.d.ts +29 -0
- package/dist/terraform-files.d.ts.map +1 -0
- package/dist/terraform-files.js +50 -0
- package/dist/terraform-files.js.map +1 -0
- package/dist/terraform-policy.d.ts +13 -0
- package/dist/terraform-policy.d.ts.map +1 -0
- package/dist/terraform-policy.js +261 -0
- package/dist/terraform-policy.js.map +1 -0
- package/dist/terraform-providers.d.ts +28 -0
- package/dist/terraform-providers.d.ts.map +1 -0
- package/dist/terraform-providers.js +68 -0
- package/dist/terraform-providers.js.map +1 -0
- package/dist/toolchain-pins.d.ts +1 -1
- package/dist/toolchain-pins.d.ts.map +1 -1
- package/dist/toolchain-pins.js +9 -2
- package/dist/toolchain-pins.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Identify OpenTofu and Terraform sources, and the analyzer configuration that would shadow the rules.
|
|
2
|
+
//
|
|
3
|
+
// The same shape as the container-file reader beside it: predicates over the working-tree inventory,
|
|
4
|
+
// so the gate discovers what a repository actually tracks rather than reading a list somebody wrote
|
|
5
|
+
// once. A `.tf` added later is analysed the day it appears rather than the day somebody remembers it.
|
|
6
|
+
const basenameOf = (file) => file.slice(file.lastIndexOf('/') + 1);
|
|
7
|
+
// `.tf` is the configuration and `.tfvars` carries the values a placeholder credential would sit in.
|
|
8
|
+
// `.tf.json` is deliberately absent: it is JSON rather than HCL, so the text rules would misread it,
|
|
9
|
+
// and the analyzer reads it anyway through the directory it is pointed at.
|
|
10
|
+
const TERRAFORM_EXTENSIONS = ['.tf', '.tfvars'];
|
|
11
|
+
/**
|
|
12
|
+
* Decide whether a path names an OpenTofu or Terraform source.
|
|
13
|
+
*
|
|
14
|
+
* An example file is not one. `terraform.tfvars.example` documents the variables an environment needs
|
|
15
|
+
* and is meant to carry placeholders, so reading it as configuration would report a project for the
|
|
16
|
+
* one file whose job is to hold the values it tells a reader to replace.
|
|
17
|
+
* @param file a repo-relative path.
|
|
18
|
+
* @returns true when the file is configuration the gate judges.
|
|
19
|
+
*/
|
|
20
|
+
export const isTerraformFile = (file) => TERRAFORM_EXTENSIONS.some((extension) => file.endsWith(extension));
|
|
21
|
+
/**
|
|
22
|
+
* Every OpenTofu or Terraform source the repository tracks.
|
|
23
|
+
* @param tracked the repo-relative paths of the tracked files.
|
|
24
|
+
* @returns the sources, ordered so a report reads the same on every machine.
|
|
25
|
+
*/
|
|
26
|
+
export const terraformFilesIn = (tracked) => tracked
|
|
27
|
+
.filter((file) => isTerraformFile(file))
|
|
28
|
+
.toSorted((left, right) => left.localeCompare(right));
|
|
29
|
+
// The names checkov reads on its own. A project that commits one can turn off the curated checks from
|
|
30
|
+
// inside the tree the gate judges, which is the shadowing the harness refuses for every other analyzer.
|
|
31
|
+
const CHECKOV_CONFIG_BASENAMES = new Set([
|
|
32
|
+
'.checkov.yaml',
|
|
33
|
+
'.checkov.yml',
|
|
34
|
+
'.checkov.json',
|
|
35
|
+
]);
|
|
36
|
+
/**
|
|
37
|
+
* Decide whether a path names a checkov configuration.
|
|
38
|
+
* @param file a repo-relative path.
|
|
39
|
+
* @returns true for a configuration checkov would read on its own, at any depth.
|
|
40
|
+
*/
|
|
41
|
+
export const isCheckovConfigFile = (file) => CHECKOV_CONFIG_BASENAMES.has(basenameOf(file));
|
|
42
|
+
/**
|
|
43
|
+
* Every checkov configuration the repository tracks.
|
|
44
|
+
* @param tracked the repo-relative paths of the tracked files.
|
|
45
|
+
* @returns the configurations, ordered so a report reads the same on every machine.
|
|
46
|
+
*/
|
|
47
|
+
export const checkovConfigsIn = (tracked) => tracked
|
|
48
|
+
.filter((file) => isCheckovConfigFile(file))
|
|
49
|
+
.toSorted((left, right) => left.localeCompare(right));
|
|
50
|
+
//# sourceMappingURL=terraform-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terraform-files.js","sourceRoot":"","sources":["../src/terraform-files.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,EAAE;AACF,qGAAqG;AACrG,oGAAoG;AACpG,sGAAsG;AAEtG,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;AAElF,qGAAqG;AACrG,qGAAqG;AACrG,2EAA2E;AAC3E,MAAM,oBAAoB,GAAsB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;AAElE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAW,EAAE,CACvD,oBAAoB,CAAC,IAAI,CAAC,CAAC,SAAiB,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AAErF;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAA0B,EAAqB,EAAE,CAChF,OAAO;KACJ,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;KACxD,QAAQ,CAAC,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA;AAEjF,sGAAsG;AACtG,wGAAwG;AACxG,MAAM,wBAAwB,GAAwB,IAAI,GAAG,CAAC;IAC5D,eAAe;IACf,cAAc;IACd,eAAe;CAChB,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAW,EAAE,CAC3D,wBAAwB,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAA;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAA0B,EAAqB,EAAE,CAChF,OAAO;KACJ,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;KAC5D,QAAQ,CAAC,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** An infrastructure defect found in a project's own configuration. */
|
|
2
|
+
export interface TerraformViolation {
|
|
3
|
+
readonly line: number;
|
|
4
|
+
readonly rule: string;
|
|
5
|
+
readonly reason: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Report every infrastructure defect a text reader can decide.
|
|
9
|
+
* @param source the file's text, as tracked.
|
|
10
|
+
* @returns one violation per defect, ordered by the line it sits on.
|
|
11
|
+
*/
|
|
12
|
+
export declare const findTerraformViolations: (source: string) => readonly TerraformViolation[];
|
|
13
|
+
//# sourceMappingURL=terraform-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terraform-policy.d.ts","sourceRoot":"","sources":["../src/terraform-policy.ts"],"names":[],"mappings":"AAiBA,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAySD;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,MAAM,KAAG,SAAS,kBAAkB,EAcnF,CAAA"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// Infrastructure defects a text reader can decide on its own, and only those.
|
|
2
|
+
//
|
|
3
|
+
// Every rule here owns a question the analyzer beside it does not answer. Where checkov has a check,
|
|
4
|
+
// checkov keeps it: one semantic rule has one owner, and a second opinion written as a regular
|
|
5
|
+
// expression would be the weaker of the two. So `AdministratorAccess`, wildcard IAM actions and a
|
|
6
|
+
// publicly reachable database are absent from this file deliberately - they are enabled as curated
|
|
7
|
+
// checks instead, where the resource graph is understood rather than guessed at from one line.
|
|
8
|
+
//
|
|
9
|
+
// `0.0.0.0/0` is read in exactly one place: the standalone ingress rule resource, whose TYPE names its
|
|
10
|
+
// direction. Everywhere else text cannot decide it. The same address is correct on egress and fatal on
|
|
11
|
+
// ingress, and an `ingress` block or an `aws_security_group_rule` carries its direction in a nested
|
|
12
|
+
// block or a sibling attribute that a line reader cannot tie to the address - so those forms stay
|
|
13
|
+
// with the analyzer, which knows which side of the rule it is looking at. The standalone resource is
|
|
14
|
+
// this file's because the analyzer does not read its idiomatic spelling at all, and its own SSH and RDP
|
|
15
|
+
// checks read a rule naming another security group as its source as open to the world.
|
|
16
|
+
import { stripComments } from './source-text.js';
|
|
17
|
+
const FIRST_LINE = 1;
|
|
18
|
+
// An argument assigned one literal value, bare or quoted, on its own line. Terraform accepts both
|
|
19
|
+
// spellings of a boolean, and a quoted string is what the other values are.
|
|
20
|
+
const assigned = (attribute, value) => new RegExp(String.raw `^[ \t]*${attribute}[ \t]*=[ \t]*"?${value}"?(?:[ \t]|$)`);
|
|
21
|
+
// The Owner definition has one id in every Azure tenant, and a role assignment may name it by id
|
|
22
|
+
// rather than by name.
|
|
23
|
+
const AZURE_OWNER_DEFINITION = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635';
|
|
24
|
+
// Each rule refuses an explicit literal and never an absence, which is what keeps every one of them
|
|
25
|
+
// free of false positives: a value that arrives through a variable or a reference never matches. The
|
|
26
|
+
// first two arguments exist only where they destroy data, and checkov ships no check for either. The
|
|
27
|
+
// last two stand in for checks that fail the rubric - `CKV_GCP_6` rejects the correct
|
|
28
|
+
// `ENCRYPTED_ONLY`, `CKV_AZURE_3` and `CKV_AZURE_91` read only the argument names azurerm retired,
|
|
29
|
+
// and every project-role check on Google Cloud also flags `viewer` or a documented deploy role,
|
|
30
|
+
// while Azure has no check for an Owner assignment at all. A checkov check that fails the rubric
|
|
31
|
+
// returns its question to text. The patterns are built once here rather than per line, so the scan
|
|
32
|
+
// is one pass over the text.
|
|
33
|
+
const FLAG_RULES = [
|
|
34
|
+
{
|
|
35
|
+
patterns: [assigned('force_destroy', 'true')],
|
|
36
|
+
rule: 'no-force-destroy',
|
|
37
|
+
reason: 'force_destroy lets a destroy remove a bucket or repository that still holds objects, so a ' +
|
|
38
|
+
'replacement-forcing edit deletes the data rather than failing - remove it, and empty the ' +
|
|
39
|
+
'store deliberately when it is genuinely meant to go',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
patterns: [assigned('skip_final_snapshot', 'true')],
|
|
43
|
+
rule: 'no-skipped-final-snapshot',
|
|
44
|
+
reason: 'skip_final_snapshot leaves a deleted database with nothing to restore from, and a destroy ' +
|
|
45
|
+
'that was never meant to reach production is exactly when that snapshot is wanted - remove it ' +
|
|
46
|
+
'and let the final snapshot be taken',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
patterns: [
|
|
50
|
+
assigned('role', 'roles/owner'),
|
|
51
|
+
assigned('role', 'roles/editor'),
|
|
52
|
+
assigned('role_definition_name', 'Owner'),
|
|
53
|
+
new RegExp(String.raw `^[ \t]*role_definition_id[ \t]*=[ \t]*"[^"]*${AZURE_OWNER_DEFINITION}"`),
|
|
54
|
+
],
|
|
55
|
+
rule: 'no-administrative-role-grant',
|
|
56
|
+
reason: 'an administrative role is granted outright, which is every permission on everything in its ' +
|
|
57
|
+
'scope - grant the specific roles the principal needs, and no more',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
patterns: [
|
|
61
|
+
assigned('ssl_mode', 'ALLOW_UNENCRYPTED_AND_ENCRYPTED'),
|
|
62
|
+
assigned('require_ssl', 'false'),
|
|
63
|
+
assigned('https_traffic_only_enabled', 'false'),
|
|
64
|
+
assigned('enable_https_traffic_only', 'false'),
|
|
65
|
+
assigned('non_ssl_port_enabled', 'true'),
|
|
66
|
+
assigned('enable_non_ssl_port', 'true'),
|
|
67
|
+
],
|
|
68
|
+
rule: 'no-unencrypted-transport',
|
|
69
|
+
reason: 'the service explicitly accepts unencrypted connections - remove the argument or set its ' +
|
|
70
|
+
'encrypted-only value; every client the harness governs speaks TLS',
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
// A quoted assignment, whatever it names. WHICH names hold a credential is decided below rather than
|
|
74
|
+
// inside the pattern: one alternation covering every spelling was unreadable and past the complexity
|
|
75
|
+
// the lint budget allows, and it is the name's segments that carry the meaning anyway.
|
|
76
|
+
const QUOTED_ASSIGNMENT = /^[ \t]*(?<name>\w+)[ \t]*=[ \t]*"(?<value>[^"]*)"/;
|
|
77
|
+
// Read by the last `_`-segment of the name, the way the credential guard in `source-security.ts` reads
|
|
78
|
+
// an identifier. `key` alone is excluded: an S3 object's `key` is a path, and reading it as a
|
|
79
|
+
// credential would report every upload in the tree - so only a QUALIFIED key counts.
|
|
80
|
+
const CREDENTIAL_WORDS = new Set([
|
|
81
|
+
'password',
|
|
82
|
+
'passphrase',
|
|
83
|
+
'secret',
|
|
84
|
+
'token',
|
|
85
|
+
'credential',
|
|
86
|
+
'credentials',
|
|
87
|
+
]);
|
|
88
|
+
const QUALIFIED_KEYS = new Set([
|
|
89
|
+
'api',
|
|
90
|
+
'access',
|
|
91
|
+
'secret',
|
|
92
|
+
'private',
|
|
93
|
+
'encryption',
|
|
94
|
+
]);
|
|
95
|
+
// A secret's CONTENT argument is named the other way round: `secret_string`, `secret_data` and
|
|
96
|
+
// `secret_binary` are what a secret-store version holds, so the first segment carries the meaning and
|
|
97
|
+
// the last says what shape the content takes. `secret_id` and `secret_arn` name a secret rather than
|
|
98
|
+
// hold one, and stay outside.
|
|
99
|
+
const SECRET_CONTENT = new Set(['string', 'data', 'binary', 'value']);
|
|
100
|
+
const PENULTIMATE = -2;
|
|
101
|
+
const isCredentialName = (name) => {
|
|
102
|
+
const segments = name.toLowerCase().split('_');
|
|
103
|
+
const first = segments.at(0) ?? '';
|
|
104
|
+
const last = segments.at(-1) ?? '';
|
|
105
|
+
if (last === 'key') {
|
|
106
|
+
return QUALIFIED_KEYS.has(segments.at(PENULTIMATE) ?? '');
|
|
107
|
+
}
|
|
108
|
+
return CREDENTIAL_WORDS.has(last) || (first === 'secret' && SECRET_CONTENT.has(last));
|
|
109
|
+
};
|
|
110
|
+
// What a placeholder looks like once punctuation and case stop mattering. An angle-bracketed value is
|
|
111
|
+
// the other conventional spelling, and it needs no vocabulary.
|
|
112
|
+
const PLACEHOLDER_VALUES = new Set([
|
|
113
|
+
'REPLACEME',
|
|
114
|
+
'CHANGEME',
|
|
115
|
+
'TODO',
|
|
116
|
+
'FIXME',
|
|
117
|
+
'XXX',
|
|
118
|
+
'PLACEHOLDER',
|
|
119
|
+
'TBD',
|
|
120
|
+
'YOURVALUEHERE',
|
|
121
|
+
]);
|
|
122
|
+
const isPlaceholder = (value) => {
|
|
123
|
+
if (value.startsWith('<') && value.endsWith('>')) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return PLACEHOLDER_VALUES.has(value.toUpperCase().replaceAll(/[^A-Z0-9]/g, ''));
|
|
127
|
+
};
|
|
128
|
+
// An analyzer's own inline skip. Without this the curated checks are advisory: one comment turns the
|
|
129
|
+
// flagship check off and the run still passes. gitleaks cannot see a placeholder either - it carries no
|
|
130
|
+
// entropy - which is why the value rule above is here rather than left to the secret scan.
|
|
131
|
+
const ANALYZER_SUPPRESSION = /#[ \t]*(?<analyzer>checkov:skip=|tfsec:ignore:|trivy:ignore:)/;
|
|
132
|
+
const flagViolations = (code) => code.split('\n').flatMap((line, index) => FLAG_RULES.filter((flag) => flag.patterns.some((pattern) => pattern.test(line))).map((flag) => ({
|
|
133
|
+
line: index + FIRST_LINE,
|
|
134
|
+
rule: flag.rule,
|
|
135
|
+
reason: flag.reason,
|
|
136
|
+
})));
|
|
137
|
+
// The attribute a line leaves as a placeholder, or nothing. Separated from the walk below so each reads
|
|
138
|
+
// as the one question it asks.
|
|
139
|
+
const placeholderNameIn = (line) => {
|
|
140
|
+
const found = QUOTED_ASSIGNMENT.exec(line);
|
|
141
|
+
if (found === null) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
const name = found.groups?.['name'] ?? '';
|
|
145
|
+
const value = found.groups?.['value'] ?? '';
|
|
146
|
+
return isCredentialName(name) && isPlaceholder(value) ? name : undefined;
|
|
147
|
+
};
|
|
148
|
+
const placeholderViolations = (code) => code.split('\n').flatMap((line, index) => {
|
|
149
|
+
const name = placeholderNameIn(line);
|
|
150
|
+
return name === undefined
|
|
151
|
+
? []
|
|
152
|
+
: [
|
|
153
|
+
{
|
|
154
|
+
line: index + FIRST_LINE,
|
|
155
|
+
rule: 'no-placeholder-secret',
|
|
156
|
+
reason: `${name} is set to a placeholder, so whatever it guards is guarded by a value written ` +
|
|
157
|
+
'into the repository - supply it from a secret store, and let the configuration name ' +
|
|
158
|
+
'the reference rather than the value',
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
});
|
|
162
|
+
// Read from the RAW text, unlike every rule above: a comment is this rule's subject rather than
|
|
163
|
+
// something to see past.
|
|
164
|
+
const suppressionViolations = (source) => source.split('\n').flatMap((line, index) => {
|
|
165
|
+
const analyzer = ANALYZER_SUPPRESSION.exec(line)?.groups?.['analyzer'];
|
|
166
|
+
return analyzer === undefined
|
|
167
|
+
? []
|
|
168
|
+
: [
|
|
169
|
+
{
|
|
170
|
+
line: index + FIRST_LINE,
|
|
171
|
+
rule: 'no-analyzer-suppression',
|
|
172
|
+
reason: `"${analyzer}" turns off a check this harness enabled, from inside the file the check ` +
|
|
173
|
+
'judges - repair what the analyzer found, because a gate a project can switch off is ' +
|
|
174
|
+
'not a gate',
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
});
|
|
178
|
+
// The rule resource is read from its header to the next line that closes or opens a top-level block.
|
|
179
|
+
// Formatted HCL closes a resource at column 0; unformatted HCL over-reads at worst the blank lines
|
|
180
|
+
// before the next block, never the next block's attributes.
|
|
181
|
+
const INGRESS_RULE_HEADER = /^[ \t]*resource[ \t]+"aws_vpc_security_group_ingress_rule"/;
|
|
182
|
+
const BLOCK_BOUNDARY = /^(?:\}|(?:resource|data|module|variable|output|locals|provider|terraform)\b)/;
|
|
183
|
+
const ANY_ADDRESS = /^[ \t]*cidr_ipv[46][ \t]*=[ \t]*"(?:0\.0\.0\.0\/0|::\/0)"/;
|
|
184
|
+
const EVERY_PROTOCOL = /^[ \t]*ip_protocol[ \t]*=[ \t]*"?-1"?/;
|
|
185
|
+
const PORT_EDGE = /^[ \t]*(?<edge>from_port|to_port)[ \t]*=[ \t]*(?<port>\d+)/;
|
|
186
|
+
const SSH_PORT = 22;
|
|
187
|
+
const RDP_PORT = 3389;
|
|
188
|
+
const LAST_PORT = 65_535;
|
|
189
|
+
const ingressRulesIn = (lines) => lines.flatMap((line, index) => {
|
|
190
|
+
if (!INGRESS_RULE_HEADER.test(line)) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const rest = lines.slice(index + 1);
|
|
194
|
+
const end = rest.findIndex((candidate) => BLOCK_BOUNDARY.test(candidate));
|
|
195
|
+
return [{ line: index + FIRST_LINE, body: end === -1 ? rest : rest.slice(0, end) }];
|
|
196
|
+
});
|
|
197
|
+
const portRangeOf = (body) => {
|
|
198
|
+
const edges = new Map(body.flatMap((line) => {
|
|
199
|
+
const found = PORT_EDGE.exec(line);
|
|
200
|
+
return found === null
|
|
201
|
+
? []
|
|
202
|
+
: [[found.groups?.['edge'] ?? '', Number(found.groups?.['port'] ?? '')]];
|
|
203
|
+
}));
|
|
204
|
+
const from = edges.get('from_port');
|
|
205
|
+
const to = edges.get('to_port');
|
|
206
|
+
return from === undefined || to === undefined ? undefined : { from, to };
|
|
207
|
+
};
|
|
208
|
+
const coversPort = (range, port) => range.from <= port && port <= range.to;
|
|
209
|
+
// What a port range exposes when its source is every address, or nothing when its ports are ones a
|
|
210
|
+
// public site legitimately opens.
|
|
211
|
+
const exposureOfRange = (range) => {
|
|
212
|
+
if (range.from === 0 && range.to === LAST_PORT) {
|
|
213
|
+
return 'every port';
|
|
214
|
+
}
|
|
215
|
+
if (coversPort(range, SSH_PORT)) {
|
|
216
|
+
return 'SSH';
|
|
217
|
+
}
|
|
218
|
+
return coversPort(range, RDP_PORT) ? 'RDP' : undefined;
|
|
219
|
+
};
|
|
220
|
+
const exposureOf = (body) => {
|
|
221
|
+
if (!body.some((line) => ANY_ADDRESS.test(line))) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
if (body.some((line) => EVERY_PROTOCOL.test(line))) {
|
|
225
|
+
return 'every port';
|
|
226
|
+
}
|
|
227
|
+
const range = portRangeOf(body);
|
|
228
|
+
return range === undefined ? undefined : exposureOfRange(range);
|
|
229
|
+
};
|
|
230
|
+
const openIngressViolations = (code) => ingressRulesIn(code.split('\n')).flatMap((rule) => {
|
|
231
|
+
const exposure = exposureOf(rule.body);
|
|
232
|
+
return exposure === undefined
|
|
233
|
+
? []
|
|
234
|
+
: [
|
|
235
|
+
{
|
|
236
|
+
line: rule.line,
|
|
237
|
+
rule: 'no-open-ingress',
|
|
238
|
+
reason: `an ingress rule admits ${exposure} from the whole internet - name the security ` +
|
|
239
|
+
'group or address range that needs it as the source',
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
});
|
|
243
|
+
/**
|
|
244
|
+
* Report every infrastructure defect a text reader can decide.
|
|
245
|
+
* @param source the file's text, as tracked.
|
|
246
|
+
* @returns one violation per defect, ordered by the line it sits on.
|
|
247
|
+
*/
|
|
248
|
+
export const findTerraformViolations = (source) => {
|
|
249
|
+
// `stripComments` covers the `//` and `/* */` forms HCL shares with the languages it was written
|
|
250
|
+
// for. It does NOT read `#`, which is HCL's own spelling - what keeps `# force_destroy = true` out
|
|
251
|
+
// of the value rules is that each pattern is anchored at the attribute, so a leading `#` cannot
|
|
252
|
+
// match. The suppression rule reads the raw text instead, because the comment is its subject.
|
|
253
|
+
const code = stripComments(source);
|
|
254
|
+
return [
|
|
255
|
+
...flagViolations(code),
|
|
256
|
+
...placeholderViolations(code),
|
|
257
|
+
...openIngressViolations(code),
|
|
258
|
+
...suppressionViolations(source),
|
|
259
|
+
].toSorted((left, right) => left.line - right.line);
|
|
260
|
+
};
|
|
261
|
+
//# sourceMappingURL=terraform-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terraform-policy.js","sourceRoot":"","sources":["../src/terraform-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** One line that names a provider: a resource, a data source, or a provider block. */
|
|
2
|
+
export interface ProviderDeclaration {
|
|
3
|
+
readonly line: number;
|
|
4
|
+
readonly provider: string;
|
|
5
|
+
/** What the line declares, as a finding names it: the resource type, or `provider "name"`. */
|
|
6
|
+
readonly subject: string;
|
|
7
|
+
readonly kind: 'resource' | 'data' | 'provider';
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Every line of a source that names a provider.
|
|
11
|
+
* @param source the file's text, as tracked.
|
|
12
|
+
* @returns the declarations, ordered by the line they sit on.
|
|
13
|
+
*/
|
|
14
|
+
export declare const providerDeclarationsIn: (source: string) => readonly ProviderDeclaration[];
|
|
15
|
+
/**
|
|
16
|
+
* The providers a set of declarations names.
|
|
17
|
+
* @param declarations declarations from one or many sources.
|
|
18
|
+
* @returns each provider once, ordered so a report reads the same on every machine.
|
|
19
|
+
*/
|
|
20
|
+
export declare const providersOf: (declarations: readonly ProviderDeclaration[]) => readonly string[];
|
|
21
|
+
/**
|
|
22
|
+
* How many resources and data sources the declarations carry - what an analyzer has to judge. A
|
|
23
|
+
* provider block configures a provider and declares nothing it could check.
|
|
24
|
+
* @param declarations declarations from one or many sources.
|
|
25
|
+
* @returns the count of resource and data declarations.
|
|
26
|
+
*/
|
|
27
|
+
export declare const resourceCountOf: (declarations: readonly ProviderDeclaration[]) => number;
|
|
28
|
+
//# sourceMappingURL=terraform-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terraform-providers.d.ts","sourceRoot":"","sources":["../src/terraform-providers.ts"],"names":[],"mappings":"AAUA,sFAAsF;AACtF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,CAAA;CAChD;AA6CD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,SAAS,mBAAmB,EAKnF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,cAAc,SAAS,mBAAmB,EAAE,KAAG,SAAS,MAAM,EAKV,CAAA;AAEhF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,cAAc,SAAS,mBAAmB,EAAE,KAAG,MAGrE,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Which providers an OpenTofu or Terraform source declares, read from block headers alone.
|
|
2
|
+
//
|
|
3
|
+
// The resource type is the key, because it is what the analyzer's own catalogue binds a check to:
|
|
4
|
+
// `aws_iam_role` belongs to `aws` whatever local name a `required_providers` block gives the provider.
|
|
5
|
+
// That block is deliberately not read. Modules inherit it from their caller, so it sits in a minority
|
|
6
|
+
// of files; its `source = "..."` line cannot be told from a module's without block parsing; and a
|
|
7
|
+
// provider declared there with no resource in the tree leaves the analyzer nothing to judge anyway.
|
|
8
|
+
// Nothing here parses HCL structure - this stays a line reader, like the policy beside it.
|
|
9
|
+
import { lineOf, stripComments } from './source-text.js';
|
|
10
|
+
// Anchored at the start of a line so HCL's own `#` comment, which `stripComments` does not read, cannot
|
|
11
|
+
// match; a `//` or `/* */` comment is blanked before the scan (see terraform-policy.ts).
|
|
12
|
+
const RESOURCE_HEADER = /^[ \t]*(?<kind>resource|data)[ \t]+"(?<type>[a-z][a-z0-9_]*)"/gm;
|
|
13
|
+
const PROVIDER_HEADER = /^[ \t]*provider[ \t]+"(?<name>[a-z][a-z0-9-]*)"/gm;
|
|
14
|
+
// A provider whose resources carry another provider's prefix. `google-beta` declares `google_*`
|
|
15
|
+
// resources, so its block names the provider the resource types already name.
|
|
16
|
+
const PROVIDER_ALIASES = new Map([['google-beta', 'google']]);
|
|
17
|
+
const providerOfType = (type) => {
|
|
18
|
+
const separator = type.indexOf('_');
|
|
19
|
+
return separator === -1 ? type : type.slice(0, separator);
|
|
20
|
+
};
|
|
21
|
+
// A matched header always carries its groups; the fallback satisfies the type checker, not a case.
|
|
22
|
+
const groupOf = (found, group) => found[group] ?? '';
|
|
23
|
+
const KIND_GROUP = 1;
|
|
24
|
+
const TYPE_GROUP = 2;
|
|
25
|
+
const NAME_GROUP = 1;
|
|
26
|
+
const resourceDeclarations = (code) => [...code.matchAll(RESOURCE_HEADER)].map((found) => {
|
|
27
|
+
const type = groupOf(found, TYPE_GROUP);
|
|
28
|
+
return {
|
|
29
|
+
line: lineOf(code, found.index),
|
|
30
|
+
provider: providerOfType(type),
|
|
31
|
+
subject: type,
|
|
32
|
+
kind: groupOf(found, KIND_GROUP) === 'data' ? 'data' : 'resource',
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
const providerBlocks = (code) => [...code.matchAll(PROVIDER_HEADER)].map((found) => {
|
|
36
|
+
const name = groupOf(found, NAME_GROUP);
|
|
37
|
+
return {
|
|
38
|
+
line: lineOf(code, found.index),
|
|
39
|
+
provider: PROVIDER_ALIASES.get(name) ?? name,
|
|
40
|
+
subject: `provider "${name}"`,
|
|
41
|
+
kind: 'provider',
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Every line of a source that names a provider.
|
|
46
|
+
* @param source the file's text, as tracked.
|
|
47
|
+
* @returns the declarations, ordered by the line they sit on.
|
|
48
|
+
*/
|
|
49
|
+
export const providerDeclarationsIn = (source) => {
|
|
50
|
+
const code = stripComments(source);
|
|
51
|
+
return [...resourceDeclarations(code), ...providerBlocks(code)].toSorted((left, right) => left.line - right.line);
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The providers a set of declarations names.
|
|
55
|
+
* @param declarations declarations from one or many sources.
|
|
56
|
+
* @returns each provider once, ordered so a report reads the same on every machine.
|
|
57
|
+
*/
|
|
58
|
+
export const providersOf = (declarations) => [
|
|
59
|
+
...new Set(declarations.map((declaration) => declaration.provider)),
|
|
60
|
+
].toSorted((left, right) => left.localeCompare(right));
|
|
61
|
+
/**
|
|
62
|
+
* How many resources and data sources the declarations carry - what an analyzer has to judge. A
|
|
63
|
+
* provider block configures a provider and declares nothing it could check.
|
|
64
|
+
* @param declarations declarations from one or many sources.
|
|
65
|
+
* @returns the count of resource and data declarations.
|
|
66
|
+
*/
|
|
67
|
+
export const resourceCountOf = (declarations) => declarations.filter((declaration) => declaration.kind !== 'provider').length;
|
|
68
|
+
//# sourceMappingURL=terraform-providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terraform-providers.js","sourceRoot":"","sources":["../src/terraform-providers.ts"],"names":[],"mappings":"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"}
|
package/dist/toolchain-pins.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** An analyzer ploaness runs in a container rather than installing from the registry. */
|
|
2
|
-
export type ContainerTool = 'gitleaks' | 'hadolint' | 'actionlint' | 'shellcheck';
|
|
2
|
+
export type ContainerTool = 'gitleaks' | 'hadolint' | 'actionlint' | 'shellcheck' | 'checkov';
|
|
3
3
|
/** The exact image each containerised analyzer runs, by digest. */
|
|
4
4
|
export declare const CONTAINER_IMAGES: Readonly<Record<ContainerTool, string>>;
|
|
5
5
|
/** Matches a reference that names image bytes AND the release they are, and only such a reference. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolchain-pins.d.ts","sourceRoot":"","sources":["../src/toolchain-pins.ts"],"names":[],"mappings":"AAGA,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"toolchain-pins.d.ts","sourceRoot":"","sources":["../src/toolchain-pins.ts"],"names":[],"mappings":"AAGA,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;AAE7F,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAmBpE,CAAA;AAED,sGAAsG;AACtG,eAAO,MAAM,aAAa,EAAE,MACsC,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAG,SAAS,MAAM,EAOxF,CAAA;AAOL;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAG3E,CAAA;AAOD;;;;;;;;;GASG;AACH,eAAO,MAAM,iBAAiB,GAAI,gBAAgB,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAG/E,CAAA;AAMD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GACpC,WAAW,MAAM,GAAG,SAAS,EAC7B,UAAU,MAAM,GAAG,SAAS,KAC3B,SAAS,MAAM,EAajB,CAAA"}
|
package/dist/toolchain-pins.js
CHANGED
|
@@ -5,9 +5,16 @@ export const CONTAINER_IMAGES = {
|
|
|
5
5
|
gitleaks: 'zricethezav/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f',
|
|
6
6
|
hadolint: 'hadolint/hadolint:v2.15.1@sha256:32dac94127fd60b7b7e3fbfc65e1383b9b5e25c9bfd7b8536de7a539fe68a12d',
|
|
7
7
|
actionlint: 'rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667',
|
|
8
|
-
//
|
|
9
|
-
//
|
|
8
|
+
// A project's operational scripts are code it runs against its own database and its own deployments,
|
|
9
|
+
// and the standard makes a check a repository implements itself into its source code. The `shell`
|
|
10
|
+
// gate reads both: a consumer's scripts, and the ones this verification command is written in.
|
|
10
11
|
shellcheck: 'koalaman/shellcheck:v0.11.0@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d',
|
|
12
|
+
// The infrastructure gate enables named checks rather than a category, and a `--check` id that does
|
|
13
|
+
// not exist matches nothing and raises no error. The digest is what makes that audit durable: the
|
|
14
|
+
// catalogue cannot change underneath it, so the enabled ids stay the ones somebody confirmed with
|
|
15
|
+
// `--list`. The provider standings beside the catalogue rest on the same listing: which providers
|
|
16
|
+
// the image has no check for at all. Re-confirm both whenever this pin moves.
|
|
17
|
+
checkov: 'bridgecrew/checkov:3.3.17@sha256:41c4701c6a56d8952e5aba7a420f871c8b70b57da94eb4f142dcdf7295bb0be3',
|
|
11
18
|
};
|
|
12
19
|
/** Matches a reference that names image bytes AND the release they are, and only such a reference. */
|
|
13
20
|
export const DIGEST_PINNED = /^[a-z0-9][a-z0-9._/-]*:[A-Za-z0-9][\w.-]*@sha256:[0-9a-f]{64}$/;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolchain-pins.js","sourceRoot":"","sources":["../src/toolchain-pins.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wEAAwE;AAKxE,mEAAmE;AACnE,MAAM,CAAC,MAAM,gBAAgB,GAA4C;IACvE,QAAQ,EACN,sGAAsG;IACxG,QAAQ,EACN,mGAAmG;IACrG,UAAU,EACR,iGAAiG;IACnG,
|
|
1
|
+
{"version":3,"file":"toolchain-pins.js","sourceRoot":"","sources":["../src/toolchain-pins.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wEAAwE;AAKxE,mEAAmE;AACnE,MAAM,CAAC,MAAM,gBAAgB,GAA4C;IACvE,QAAQ,EACN,sGAAsG;IACxG,QAAQ,EACN,mGAAmG;IACrG,UAAU,EACR,iGAAiG;IACnG,qGAAqG;IACrG,kGAAkG;IAClG,+FAA+F;IAC/F,UAAU,EACR,qGAAqG;IACvG,oGAAoG;IACpG,kGAAkG;IAClG,kGAAkG;IAClG,kGAAkG;IAClG,8EAA8E;IAC9E,OAAO,EACL,mGAAmG;CACtG,CAAA;AAED,sGAAsG;AACtG,MAAM,CAAC,MAAM,aAAa,GACxB,gEAAgE,CAAA;AAElE;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAwC,EAAqB,EAAE,CAChG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;KACnB,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,CAA4B,EAAW,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;KAC7F,GAAG,CACF,CAAC,CAAC,IAAI,EAAE,SAAS,CAA4B,EAAU,EAAE,CACvD,GAAG,IAAI,UAAU,SAAS,yDAAyD;IACnF,wCAAwC,CAC3C,CAAA;AAEL,0FAA0F;AAC1F,wGAAwG;AACxG,sGAAsG;AACtG,MAAM,aAAa,GAAW,OAAO,CAAA;AAErC;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,WAA+B,EAAsB,EAAE;IACtF,MAAM,KAAK,GAA2B,aAAa,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAA;IAC3E,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACtD,CAAC,CAAA;AAED,oGAAoG;AACpG,iGAAiG;AACjG,0DAA0D;AAC1D,MAAM,cAAc,GAAW,4BAA4B,CAAA;AAE3D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,cAAkC,EAAsB,EAAE;IAC1F,MAAM,KAAK,GAA2B,cAAc,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAA;IAC/E,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;AACnB,CAAC,CAAA;AAED,qGAAqG;AACrG,oGAAoG;AACpG,MAAM,eAAe,GAAW,qBAAqB,CAAA;AAErD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,SAA6B,EAC7B,QAA4B,EACT,EAAE;IACrB,MAAM,KAAK,GAA2B,eAAe,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAA;IAC3E,MAAM,OAAO,GAAuB,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;IAC9C,gGAAgG;IAChG,oGAAoG;IACpG,8FAA8F;IAC9F,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5E,OAAO,EAAE,CAAA;IACX,CAAC;IACD,OAAO;QACL,QAAQ,OAAO,mDAAmD,QAAQ,IAAI;YAC5E,0FAA0F;KAC7F,CAAA;AACH,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ploaness/governance",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Pure, unit-tested governance rules for ploaness. No filesystem, process, or network access.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Herman Ciechanowiec <herman@ciechanowiec.eu>",
|