@aws/cloudformation-validate 1.9.0-beta → 1.10.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/README.md +252 -87
- package/THIRD-PARTY-LICENSES.txt +79 -0
- package/bindings_wasm.d.ts +114 -111
- package/bindings_wasm.js +121 -17
- package/bindings_wasm_bg.wasm +0 -0
- package/bindings_wasm_bg.wasm.d.ts +10 -11
- package/index.d.ts +72 -10
- package/index.js +211 -9
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
Diagnostic,
|
|
3
|
+
ValidationReport,
|
|
3
4
|
DiagnosticModel,
|
|
4
5
|
AdditionalSchemaSource,
|
|
5
6
|
ExternalRuleSource,
|
|
@@ -8,8 +9,6 @@ import type {
|
|
|
8
9
|
ResolvedResource,
|
|
9
10
|
RuleInfo,
|
|
10
11
|
SourceSpan,
|
|
11
|
-
StandardDiagnostic,
|
|
12
|
-
StandardReport,
|
|
13
12
|
ValidateConfig,
|
|
14
13
|
} from './bindings_wasm';
|
|
15
14
|
export type {
|
|
@@ -29,14 +28,12 @@ export type {
|
|
|
29
28
|
ResourceRef,
|
|
30
29
|
RelatedResource,
|
|
31
30
|
ViolationContext,
|
|
32
|
-
|
|
33
|
-
DetailedDiagnostic,
|
|
31
|
+
Diagnostic,
|
|
34
32
|
PhaseMetric,
|
|
35
33
|
PerformanceMetrics,
|
|
36
34
|
Summary,
|
|
37
35
|
ReportMetadata,
|
|
38
|
-
|
|
39
|
-
DetailedReport,
|
|
36
|
+
ValidationReport,
|
|
40
37
|
PseudoParameterOverrides,
|
|
41
38
|
ValidateConfig,
|
|
42
39
|
ExternalRuleSource,
|
|
@@ -79,9 +76,55 @@ export type JsonValue =
|
|
|
79
76
|
| {
|
|
80
77
|
[key: string]: JsonValue;
|
|
81
78
|
};
|
|
79
|
+
export type AwsCliOperationKind =
|
|
80
|
+
| 'READ_ONLY'
|
|
81
|
+
| 'CLOUD_FORMATION_CREATE'
|
|
82
|
+
| 'CLOUD_FORMATION_UPDATE'
|
|
83
|
+
| 'CLOUD_FORMATION_DELETE'
|
|
84
|
+
| 'DATA_PLANE_MUTATION'
|
|
85
|
+
| 'UNMAPPED_MUTATION';
|
|
86
|
+
export type AwsCliCommandValidationStatus = 'VALIDATED' | 'SKIPPED';
|
|
87
|
+
export type AwsCliTemplateSource =
|
|
88
|
+
'TEMPLATE_BODY' | 'CLOUD_CONTROL_DESIRED_STATE' | 'SYNTHESIZED_CREATE' | 'SYNTHESIZED_UPDATE';
|
|
89
|
+
export interface AwsCliCommandOptions {
|
|
90
|
+
servicePrefix?: string;
|
|
91
|
+
httpMethod?: string;
|
|
92
|
+
isReadOnly?: boolean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Service, operation, and input values for one AWS CLI command.
|
|
96
|
+
*
|
|
97
|
+
* `serviceName` is the canonical botocore service name and is normalized only
|
|
98
|
+
* for ASCII case. Callers adapting an SDK request must translate its native
|
|
99
|
+
* service identity before constructing this request; endpoint and signing-name
|
|
100
|
+
* aliases are never guessed by the validation core.
|
|
101
|
+
*/
|
|
102
|
+
export declare class AwsCliCommand {
|
|
103
|
+
readonly serviceName: string;
|
|
104
|
+
readonly operationName: string;
|
|
105
|
+
readonly parameters: Record<string, unknown>;
|
|
106
|
+
readonly servicePrefix?: string;
|
|
107
|
+
readonly httpMethod?: string;
|
|
108
|
+
readonly isReadOnly?: boolean;
|
|
109
|
+
constructor(
|
|
110
|
+
serviceName: string,
|
|
111
|
+
operationName: string,
|
|
112
|
+
parameters: Record<string, unknown>,
|
|
113
|
+
options?: AwsCliCommandOptions,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
export interface AwsCliCommandValidation {
|
|
117
|
+
operationKind: AwsCliOperationKind;
|
|
118
|
+
status: AwsCliCommandValidationStatus;
|
|
119
|
+
templateSource: AwsCliTemplateSource | null;
|
|
120
|
+
resourceTypes: string[];
|
|
121
|
+
reason: string;
|
|
122
|
+
report: ValidationReport | null;
|
|
123
|
+
template: Uint8Array | null;
|
|
124
|
+
}
|
|
82
125
|
export interface Engine {
|
|
83
|
-
|
|
84
|
-
|
|
126
|
+
validateTemplate(template: TemplateFile, config?: ValidateConfig): ValidationReport;
|
|
127
|
+
validateAwsCliCommand(request: AwsCliCommand): AwsCliCommandValidation;
|
|
85
128
|
listRules(): RuleInfo[];
|
|
86
129
|
engineName(): string;
|
|
87
130
|
free(): void;
|
|
@@ -119,6 +162,24 @@ export interface EngineConfig {
|
|
|
119
162
|
*/
|
|
120
163
|
schemaValidatorConfig?: SchemaValidatorConfig;
|
|
121
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Configuration for the {@link CompositeEngine}. The built-in rules are always
|
|
167
|
+
* evaluated by the engine's fixed built-in evaluator, so these fields only layer
|
|
168
|
+
* external rules on top - there is no engine-native custom-rule field.
|
|
169
|
+
*/
|
|
170
|
+
export interface CompositeEngineConfig {
|
|
171
|
+
/** Custom Rego rules layered on top of the built-in rules. */
|
|
172
|
+
regoRules?: RuleSource[];
|
|
173
|
+
/** Custom CEL rules layered on top of the built-in rules. */
|
|
174
|
+
celRules?: RuleSource[];
|
|
175
|
+
/** CloudFormation Guard DSL rules layered on top of the built-in rules. */
|
|
176
|
+
guardRules?: RuleSource[];
|
|
177
|
+
/**
|
|
178
|
+
* Optional schema validator configuration, observed by both the built-in and
|
|
179
|
+
* external rule evaluation.
|
|
180
|
+
*/
|
|
181
|
+
schemaValidatorConfig?: SchemaValidatorConfig;
|
|
182
|
+
}
|
|
122
183
|
/**
|
|
123
184
|
* Configuration for the schema validator. Additional schemas are merged on top
|
|
124
185
|
* of the bundled CloudFormation provider schemas before schema validation.
|
|
@@ -150,9 +211,10 @@ export declare class SchemaValidator {
|
|
|
150
211
|
constructor(config?: SchemaValidatorConfig);
|
|
151
212
|
listRules(): RuleInfo[];
|
|
152
213
|
schemaCount(): number;
|
|
153
|
-
validate(template: TemplateFile, region?: string):
|
|
214
|
+
validate(template: TemplateFile, region?: string): Diagnostic[];
|
|
154
215
|
free(): void;
|
|
155
216
|
}
|
|
156
217
|
export declare const RegoEngine: new (config?: EngineConfig) => Engine;
|
|
157
218
|
export declare const CelEngine: new (config?: EngineConfig) => Engine;
|
|
219
|
+
export declare const CompositeEngine: new (config?: CompositeEngineConfig) => Engine;
|
|
158
220
|
export declare function version(): string;
|
package/index.js
CHANGED
|
@@ -1,15 +1,203 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.CompositeEngine =
|
|
4
|
+
exports.CelEngine =
|
|
4
5
|
exports.RegoEngine =
|
|
5
6
|
exports.SchemaValidator =
|
|
6
7
|
exports.TemplateModel =
|
|
7
8
|
exports.SchemaFile =
|
|
8
9
|
exports.RuleFile =
|
|
9
10
|
exports.TemplateFile =
|
|
11
|
+
exports.AwsCliCommand =
|
|
10
12
|
void 0;
|
|
11
13
|
exports.version = version;
|
|
12
14
|
const fs_1 = require('fs');
|
|
15
|
+
/**
|
|
16
|
+
* Service, operation, and input values for one AWS CLI command.
|
|
17
|
+
*
|
|
18
|
+
* `serviceName` is the canonical botocore service name and is normalized only
|
|
19
|
+
* for ASCII case. Callers adapting an SDK request must translate its native
|
|
20
|
+
* service identity before constructing this request; endpoint and signing-name
|
|
21
|
+
* aliases are never guessed by the validation core.
|
|
22
|
+
*/
|
|
23
|
+
class AwsCliCommand {
|
|
24
|
+
constructor(serviceName, operationName, parameters, options = {}) {
|
|
25
|
+
this.serviceName = serviceName;
|
|
26
|
+
this.operationName = operationName;
|
|
27
|
+
if (!isPlainRecord(parameters)) {
|
|
28
|
+
throw new TypeError('parameters must be a plain object with string keys');
|
|
29
|
+
}
|
|
30
|
+
const copiedParameters = Object.create(null);
|
|
31
|
+
for (const key of Reflect.ownKeys(parameters)) {
|
|
32
|
+
if (typeof key !== 'string') {
|
|
33
|
+
throw new TypeError('request parameter names must be strings');
|
|
34
|
+
}
|
|
35
|
+
const descriptor = Object.getOwnPropertyDescriptor(parameters, key);
|
|
36
|
+
if (descriptor === undefined || !('value' in descriptor)) {
|
|
37
|
+
throw new TypeError(`request parameter ${JSON.stringify(key)} must be a value property`);
|
|
38
|
+
}
|
|
39
|
+
copiedParameters[key] = descriptor.value;
|
|
40
|
+
}
|
|
41
|
+
this.parameters = copiedParameters;
|
|
42
|
+
this.servicePrefix = options.servicePrefix;
|
|
43
|
+
this.httpMethod = options.httpMethod;
|
|
44
|
+
this.isReadOnly = options.isReadOnly;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.AwsCliCommand = AwsCliCommand;
|
|
48
|
+
const MIN_SIGNED_64 = -(1n << 63n);
|
|
49
|
+
const MAX_SIGNED_64 = (1n << 63n) - 1n;
|
|
50
|
+
const MAX_UNSIGNED_64 = (1n << 64n) - 1n;
|
|
51
|
+
const MAX_REQUEST_VALUE_DEPTH = 64;
|
|
52
|
+
const DATE_GET_TIME = Date.prototype.getTime;
|
|
53
|
+
const DATE_TO_ISO_STRING = Date.prototype.toISOString;
|
|
54
|
+
const UINT8_ARRAY_FOR_EACH = Uint8Array.prototype.forEach;
|
|
55
|
+
function isPlainRecord(value) {
|
|
56
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const prototype = Object.getPrototypeOf(value);
|
|
60
|
+
return prototype === Object.prototype || prototype === null;
|
|
61
|
+
}
|
|
62
|
+
function unsupportedValue(typeName) {
|
|
63
|
+
return { type: 'UNSUPPORTED', type_name: typeName };
|
|
64
|
+
}
|
|
65
|
+
function encodeAwsCliValue(value, depth = 0, ancestors = new Set()) {
|
|
66
|
+
if (depth > MAX_REQUEST_VALUE_DEPTH) {
|
|
67
|
+
return unsupportedValue('recursion depth exceeded');
|
|
68
|
+
}
|
|
69
|
+
if (value === null) {
|
|
70
|
+
return { type: 'NULL' };
|
|
71
|
+
}
|
|
72
|
+
if (typeof value === 'boolean') {
|
|
73
|
+
return { type: 'BOOLEAN', value };
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === 'number') {
|
|
76
|
+
if (!Number.isFinite(value)) {
|
|
77
|
+
return unsupportedValue('non-finite floating-point number');
|
|
78
|
+
}
|
|
79
|
+
if (Number.isInteger(value)) {
|
|
80
|
+
return Number.isSafeInteger(value)
|
|
81
|
+
? { type: 'INTEGER', value }
|
|
82
|
+
: unsupportedValue('integer outside the JavaScript safe range');
|
|
83
|
+
}
|
|
84
|
+
return { type: 'NUMBER', value };
|
|
85
|
+
}
|
|
86
|
+
if (typeof value === 'bigint') {
|
|
87
|
+
if (value >= MIN_SIGNED_64 && value <= MAX_SIGNED_64) {
|
|
88
|
+
return { type: 'INTEGER', value };
|
|
89
|
+
}
|
|
90
|
+
if (value >= 0n && value <= MAX_UNSIGNED_64) {
|
|
91
|
+
return { type: 'UNSIGNED_INTEGER', value };
|
|
92
|
+
}
|
|
93
|
+
return unsupportedValue('integer outside the 64-bit request range');
|
|
94
|
+
}
|
|
95
|
+
if (typeof value === 'string') {
|
|
96
|
+
return { type: 'STRING', value };
|
|
97
|
+
}
|
|
98
|
+
if (value instanceof Uint8Array) {
|
|
99
|
+
const bytes = [];
|
|
100
|
+
try {
|
|
101
|
+
UINT8_ARRAY_FOR_EACH.call(value, (byte) => {
|
|
102
|
+
bytes.push(byte);
|
|
103
|
+
});
|
|
104
|
+
} catch {
|
|
105
|
+
return unsupportedValue('invalid Uint8Array');
|
|
106
|
+
}
|
|
107
|
+
return { type: 'BYTES', value: bytes };
|
|
108
|
+
}
|
|
109
|
+
if (value instanceof Date) {
|
|
110
|
+
try {
|
|
111
|
+
const timestamp = DATE_GET_TIME.call(value);
|
|
112
|
+
return Number.isFinite(timestamp)
|
|
113
|
+
? { type: 'STRING', value: DATE_TO_ISO_STRING.call(value) }
|
|
114
|
+
: unsupportedValue('invalid Date');
|
|
115
|
+
} catch {
|
|
116
|
+
return unsupportedValue('invalid Date');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(value)) {
|
|
120
|
+
if (ancestors.has(value)) {
|
|
121
|
+
return unsupportedValue('cyclic array');
|
|
122
|
+
}
|
|
123
|
+
ancestors.add(value);
|
|
124
|
+
try {
|
|
125
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, 'length');
|
|
126
|
+
if (
|
|
127
|
+
lengthDescriptor === undefined ||
|
|
128
|
+
!('value' in lengthDescriptor) ||
|
|
129
|
+
!Number.isSafeInteger(lengthDescriptor.value) ||
|
|
130
|
+
lengthDescriptor.value < 0
|
|
131
|
+
) {
|
|
132
|
+
return unsupportedValue('array with invalid length');
|
|
133
|
+
}
|
|
134
|
+
const items = [];
|
|
135
|
+
for (let index = 0; index < lengthDescriptor.value; index += 1) {
|
|
136
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
137
|
+
if (descriptor === undefined) {
|
|
138
|
+
return unsupportedValue('sparse array');
|
|
139
|
+
}
|
|
140
|
+
if (!('value' in descriptor)) {
|
|
141
|
+
return unsupportedValue('array with accessor elements');
|
|
142
|
+
}
|
|
143
|
+
items.push(encodeAwsCliValue(descriptor.value, depth + 1, ancestors));
|
|
144
|
+
}
|
|
145
|
+
return { type: 'ARRAY', items };
|
|
146
|
+
} finally {
|
|
147
|
+
ancestors.delete(value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (isPlainRecord(value)) {
|
|
151
|
+
if (ancestors.has(value)) {
|
|
152
|
+
return unsupportedValue('cyclic object');
|
|
153
|
+
}
|
|
154
|
+
ancestors.add(value);
|
|
155
|
+
try {
|
|
156
|
+
const entries = Object.create(null);
|
|
157
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
158
|
+
if (typeof key !== 'string') {
|
|
159
|
+
return unsupportedValue('mapping with non-string keys');
|
|
160
|
+
}
|
|
161
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
162
|
+
if (descriptor === undefined || !('value' in descriptor)) {
|
|
163
|
+
return unsupportedValue('mapping with accessor properties');
|
|
164
|
+
}
|
|
165
|
+
entries[key] = encodeAwsCliValue(descriptor.value, depth + 1, ancestors);
|
|
166
|
+
}
|
|
167
|
+
return { type: 'OBJECT', entries };
|
|
168
|
+
} finally {
|
|
169
|
+
ancestors.delete(value);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return unsupportedValue(typeof value);
|
|
173
|
+
}
|
|
174
|
+
function toWireAwsCliCommand(request) {
|
|
175
|
+
const parameters = Object.create(null);
|
|
176
|
+
for (const [name, value] of Object.entries(request.parameters)) {
|
|
177
|
+
try {
|
|
178
|
+
parameters[name] = encodeAwsCliValue(value);
|
|
179
|
+
} catch {
|
|
180
|
+
parameters[name] = unsupportedValue('request value inspection failed');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
serviceName: request.serviceName,
|
|
185
|
+
operationName: request.operationName,
|
|
186
|
+
parameters,
|
|
187
|
+
...(request.servicePrefix === undefined ? {} : { servicePrefix: request.servicePrefix }),
|
|
188
|
+
...(request.httpMethod === undefined ? {} : { httpMethod: request.httpMethod }),
|
|
189
|
+
...(request.isReadOnly === undefined ? {} : { isReadOnly: request.isReadOnly }),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function fromWireAwsCliCommandValidation(validation) {
|
|
193
|
+
const template = validation.template;
|
|
194
|
+
return {
|
|
195
|
+
...validation,
|
|
196
|
+
templateSource: validation.templateSource ?? null,
|
|
197
|
+
report: validation.report ?? null,
|
|
198
|
+
template: template == null ? null : Uint8Array.from(template),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
13
201
|
const bridge = require('./bindings_wasm');
|
|
14
202
|
class TemplateFile {
|
|
15
203
|
constructor(path) {
|
|
@@ -62,6 +250,16 @@ function toWasmEngineConfig(config) {
|
|
|
62
250
|
: undefined,
|
|
63
251
|
};
|
|
64
252
|
}
|
|
253
|
+
function toWasmCompositeEngineConfig(config) {
|
|
254
|
+
return {
|
|
255
|
+
regoRules: toExternalRuleSources(config?.regoRules),
|
|
256
|
+
celRules: toExternalRuleSources(config?.celRules),
|
|
257
|
+
guardRules: toExternalRuleSources(config?.guardRules),
|
|
258
|
+
schemaValidatorConfig: config?.schemaValidatorConfig
|
|
259
|
+
? toWasmSchemaValidatorConfig(config.schemaValidatorConfig)
|
|
260
|
+
: undefined,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
65
263
|
function toWasmSchemaValidatorConfig(config) {
|
|
66
264
|
return {
|
|
67
265
|
additionalSchemas: toAdditionalSchemas(config?.additionalSchemas),
|
|
@@ -126,16 +324,19 @@ class SchemaValidator {
|
|
|
126
324
|
}
|
|
127
325
|
}
|
|
128
326
|
exports.SchemaValidator = SchemaValidator;
|
|
129
|
-
function createEngineClass(WasmClass) {
|
|
327
|
+
function createEngineClass(WasmClass, toWasmConfig) {
|
|
130
328
|
return class {
|
|
131
329
|
constructor(config) {
|
|
132
|
-
this.inner = new WasmClass(
|
|
330
|
+
this.inner = new WasmClass(toWasmConfig(config));
|
|
133
331
|
}
|
|
134
|
-
|
|
135
|
-
return this.inner.
|
|
332
|
+
validateTemplate(template, config) {
|
|
333
|
+
return this.inner.validateTemplate(template.readBytes(), config ?? {}, template.path);
|
|
136
334
|
}
|
|
137
|
-
|
|
138
|
-
|
|
335
|
+
validateAwsCliCommand(request) {
|
|
336
|
+
if (!(request instanceof AwsCliCommand)) {
|
|
337
|
+
throw new TypeError('request must be an AwsCliCommand');
|
|
338
|
+
}
|
|
339
|
+
return fromWireAwsCliCommandValidation(this.inner.validateAwsCliCommand(toWireAwsCliCommand(request)));
|
|
139
340
|
}
|
|
140
341
|
listRules() {
|
|
141
342
|
return this.inner.listRules();
|
|
@@ -148,8 +349,9 @@ function createEngineClass(WasmClass) {
|
|
|
148
349
|
}
|
|
149
350
|
};
|
|
150
351
|
}
|
|
151
|
-
exports.RegoEngine = createEngineClass(bridge.WasmRegoEngine);
|
|
152
|
-
exports.CelEngine = createEngineClass(bridge.WasmCelEngine);
|
|
352
|
+
exports.RegoEngine = createEngineClass(bridge.WasmRegoEngine, toWasmEngineConfig);
|
|
353
|
+
exports.CelEngine = createEngineClass(bridge.WasmCelEngine, toWasmEngineConfig);
|
|
354
|
+
exports.CompositeEngine = createEngineClass(bridge.WasmCompositeEngine, toWasmCompositeEngineConfig);
|
|
153
355
|
function version() {
|
|
154
356
|
return bridge.version();
|
|
155
357
|
}
|