@intentius/chant-lexicon-azure 0.124.31 → 0.124.33
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/actions/appservice.js +7 -0
- package/dist/actions/compute.js +9 -0
- package/dist/actions/container.js +14 -0
- package/dist/actions/identity.js +11 -0
- package/dist/actions/index.js +11 -0
- package/dist/actions/keyvault.js +12 -0
- package/dist/actions/network.js +9 -0
- package/dist/actions/sql.js +9 -0
- package/dist/actions/storage.js +15 -0
- package/dist/api/read-client.js +146 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +83 -0
- package/dist/codegen/generate-cli.js +11 -0
- package/dist/codegen/generate-lexicon.js +77 -0
- package/dist/codegen/generate-typescript.js +163 -0
- package/dist/codegen/generate.js +106 -0
- package/dist/codegen/naming.js +139 -0
- package/dist/codegen/package.js +64 -0
- package/dist/composites/aks-cluster.js +68 -0
- package/dist/composites/app-service.js +62 -0
- package/dist/composites/application-gateway.js +107 -0
- package/dist/composites/catalog.js +856 -0
- package/dist/composites/container-instance.js +36 -0
- package/dist/composites/container-registry.js +62 -0
- package/dist/composites/cosmos-database.js +56 -0
- package/dist/composites/from-arm.js +47 -0
- package/dist/composites/function-app.js +57 -0
- package/dist/composites/index.js +16 -0
- package/dist/composites/keyvault.js +69 -0
- package/dist/composites/landing-zone.js +119 -0
- package/dist/composites/private-endpoint.js +61 -0
- package/dist/composites/redis-cache.js +21 -0
- package/dist/composites/service-bus-pipeline.js +54 -0
- package/dist/composites/sql-database.js +62 -0
- package/dist/composites/storage-account.js +59 -0
- package/dist/composites/vm-linux.js +139 -0
- package/dist/composites/vnet-default.js +102 -0
- package/dist/coverage.js +28 -0
- package/dist/deep-observe.js +356 -0
- package/dist/default-tags.js +50 -0
- package/dist/deploy-scopes.d.ts.map +1 -1
- package/dist/deploy-scopes.js +157 -0
- package/dist/describe-resources.js +187 -0
- package/dist/detect.js +17 -0
- package/dist/export-resources.js +108 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/generated/index.js +16598 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/governance.js +144 -0
- package/dist/health.d.ts +42 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +137 -0
- package/dist/import/generator.js +140 -0
- package/dist/import/live-export.js +39 -0
- package/dist/import/parser.js +192 -0
- package/dist/index.js +44 -0
- package/dist/integrity.json +5 -5
- package/dist/intrinsics.js +179 -0
- package/dist/lint/audit-catalog.js +36 -0
- package/dist/lint/audit-lineage.js +71 -0
- package/dist/lint/post-synth/arm-refs.d.ts +1 -0
- package/dist/lint/post-synth/arm-refs.d.ts.map +1 -1
- package/dist/lint/post-synth/arm-refs.js +96 -0
- package/dist/lint/post-synth/azr010.js +45 -0
- package/dist/lint/post-synth/azr011.js +43 -0
- package/dist/lint/post-synth/azr012.js +41 -0
- package/dist/lint/post-synth/azr013.js +50 -0
- package/dist/lint/post-synth/azr014.js +49 -0
- package/dist/lint/post-synth/azr015.js +66 -0
- package/dist/lint/post-synth/azr016.js +37 -0
- package/dist/lint/post-synth/azr017.js +37 -0
- package/dist/lint/post-synth/azr018.js +42 -0
- package/dist/lint/post-synth/azr019.js +41 -0
- package/dist/lint/post-synth/azr020.js +37 -0
- package/dist/lint/post-synth/azr021.js +36 -0
- package/dist/lint/post-synth/azr022.js +39 -0
- package/dist/lint/post-synth/azr023.js +39 -0
- package/dist/lint/post-synth/azr024.js +39 -0
- package/dist/lint/post-synth/azr025.js +37 -0
- package/dist/lint/post-synth/azr026.js +38 -0
- package/dist/lint/post-synth/azr027.js +37 -0
- package/dist/lint/post-synth/azr028.js +37 -0
- package/dist/lint/post-synth/azr029.js +38 -0
- package/dist/lint/post-synth/azr030.js +44 -0
- package/dist/lint/post-synth/azr031.js +36 -0
- package/dist/lint/post-synth/azr032.js +53 -0
- package/dist/lint/post-synth/azr033.js +35 -0
- package/dist/lint/post-synth/index.js +50 -0
- package/dist/lint/rules/hardcoded-location.js +82 -0
- package/dist/lint/rules/index.js +6 -0
- package/dist/lint/rules/nsg-wildcard.js +44 -0
- package/dist/lint/rules/storage-https.js +62 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +44 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +6 -1
- package/dist/okf/types/containerApps.md +5 -0
- package/dist/op/activities/az-apply.js +563 -0
- package/dist/op/activities/azure.js +48 -0
- package/dist/op/activities/floci-az.js +37 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +70 -0
- package/dist/ownership.js +13 -0
- package/dist/package-cli.js +21 -0
- package/dist/parameter.js +13 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +478 -0
- package/dist/pseudo.js +40 -0
- package/dist/rules/arm-refs.ts +5 -3
- package/dist/serializer.d.ts +7 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +543 -0
- package/dist/spec/api-versions.js +130 -0
- package/dist/spec/fetch.js +196 -0
- package/dist/spec/parse.d.ts.map +1 -1
- package/dist/spec/parse.js +318 -0
- package/dist/taggable.js +34 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +53 -0
- package/package.json +8 -8
- package/src/attributes.test.ts +17 -0
- package/src/codegen/generate-typescript.ts +1 -1
- package/src/codegen/generate.ts +1 -1
- package/src/codegen/package.ts +2 -2
- package/src/deploy-scopes.ts +2 -1
- package/src/generated/index.d.ts +5 -0
- package/src/generated/index.ts +1 -1
- package/src/generated/lexicon-azure.json +6 -1
- package/src/health.test.ts +66 -0
- package/src/health.ts +162 -0
- package/src/lint/post-synth/arm-refs.ts +5 -3
- package/src/lint/post-synth/post-synth.test.ts +1 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/plugin.ts +11 -2
- package/src/serializer-names.test.ts +83 -0
- package/src/serializer-outputs.test.ts +65 -0
- package/src/serializer.ts +112 -30
- package/src/spec/parse.test.ts +15 -0
- package/src/spec/parse.ts +10 -0
- package/src/taggable.ts +2 -2
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility for extracting ARM template resource references
|
|
3
|
+
* from template properties.
|
|
4
|
+
*
|
|
5
|
+
* Used by AZR010 (redundant DependsOn) and other post-synth checks.
|
|
6
|
+
*
|
|
7
|
+
* ARM templates use bracket expressions like [resourceId(...)],
|
|
8
|
+
* [reference(...)], and [concat(...)] to reference other resources.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Parse a serialized ARM template from build output.
|
|
12
|
+
* Accepts either a raw string or a SerializerResult (extracts primary).
|
|
13
|
+
*/
|
|
14
|
+
export function parseArmTemplate(output) {
|
|
15
|
+
const raw = typeof output === "string" ? output : output.primary;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(raw);
|
|
18
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
19
|
+
return parsed;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Not valid JSON
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract resource names referenced via bracket expressions in a value.
|
|
29
|
+
*
|
|
30
|
+
* Parses ARM bracket expressions like:
|
|
31
|
+
* - [resourceId('Microsoft.Storage/storageAccounts', 'myStorage')]
|
|
32
|
+
* - [reference('myStorage')]
|
|
33
|
+
* - [reference('myStorage').primaryEndpoints.blob]
|
|
34
|
+
*
|
|
35
|
+
* Returns the set of resource names found in these references.
|
|
36
|
+
*/
|
|
37
|
+
export function findArmResourceRefs(value) {
|
|
38
|
+
const refs = new Set();
|
|
39
|
+
walkArmValue(value, refs);
|
|
40
|
+
return refs;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Check if a string value is an ARM bracket expression.
|
|
44
|
+
*/
|
|
45
|
+
export function isBracketExpression(value) {
|
|
46
|
+
return typeof value === "string" && value.startsWith("[") && value.endsWith("]");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Extract resource names from an ARM bracket expression string.
|
|
50
|
+
*
|
|
51
|
+
* Handles:
|
|
52
|
+
* - [resourceId('Type', 'name')] -> extracts 'name'
|
|
53
|
+
* - [resourceId('Type', 'a', 'b')] -> extracts 'a/b'
|
|
54
|
+
* - [reference('name')] -> extracts 'name'
|
|
55
|
+
* - [reference('name').prop] -> extracts 'name'
|
|
56
|
+
*/
|
|
57
|
+
export function extractRefsFromExpression(expr) {
|
|
58
|
+
const refs = [];
|
|
59
|
+
// Match resourceId('Type', 'name', ...): the name is every quoted argument
|
|
60
|
+
// after the type, joined with "/" for a child resource ('vnet', 'subnet').
|
|
61
|
+
const resourceIdPattern = /resourceId\s*\(\s*'[^']+'((?:\s*,\s*'[^']+')+)\s*\)/g;
|
|
62
|
+
let match;
|
|
63
|
+
while ((match = resourceIdPattern.exec(expr)) !== null) {
|
|
64
|
+
refs.push([...match[1].matchAll(/'([^']+)'/g)].map((m) => m[1]).join("/"));
|
|
65
|
+
}
|
|
66
|
+
// Match reference('name') — extract the name
|
|
67
|
+
const referencePattern = /reference\s*\(\s*'([^']+)'\s*\)/g;
|
|
68
|
+
while ((match = referencePattern.exec(expr)) !== null) {
|
|
69
|
+
refs.push(match[1]);
|
|
70
|
+
}
|
|
71
|
+
return refs;
|
|
72
|
+
}
|
|
73
|
+
function walkArmValue(value, refs) {
|
|
74
|
+
if (value === null || value === undefined)
|
|
75
|
+
return;
|
|
76
|
+
if (typeof value === "string") {
|
|
77
|
+
if (isBracketExpression(value)) {
|
|
78
|
+
for (const ref of extractRefsFromExpression(value)) {
|
|
79
|
+
refs.add(ref);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
for (const item of value) {
|
|
86
|
+
walkArmValue(item, refs);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (typeof value === "object") {
|
|
91
|
+
const obj = value;
|
|
92
|
+
for (const val of Object.values(obj)) {
|
|
93
|
+
walkArmValue(val, refs);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR010: Redundant DependsOn
|
|
3
|
+
*
|
|
4
|
+
* Detects dependsOn entries that are already implied by reference() or
|
|
5
|
+
* resourceId() bracket expressions in the resource's properties. ARM
|
|
6
|
+
* automatically creates dependencies for these references, making
|
|
7
|
+
* explicit dependsOn redundant.
|
|
8
|
+
*/
|
|
9
|
+
import { parseArmTemplate, findArmResourceRefs, extractRefsFromExpression } from "./arm-refs.js";
|
|
10
|
+
export const azr010 = {
|
|
11
|
+
id: "AZR010",
|
|
12
|
+
description: "Redundant dependsOn — target is already referenced via reference() or resourceId() in properties",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
if (!resource.dependsOn || resource.dependsOn.length === 0)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
// Find all refs in properties
|
|
24
|
+
const propertyRefs = findArmResourceRefs(resource.properties);
|
|
25
|
+
for (const dep of resource.dependsOn) {
|
|
26
|
+
// dependsOn entries may be bracket expressions like [resourceId('Type', 'name')]
|
|
27
|
+
// or plain resource names
|
|
28
|
+
const depNames = dep.startsWith("[") ? extractRefsFromExpression(dep) : [dep];
|
|
29
|
+
for (const depName of depNames) {
|
|
30
|
+
if (propertyRefs.has(depName)) {
|
|
31
|
+
diagnostics.push({
|
|
32
|
+
checkId: "AZR010",
|
|
33
|
+
severity: "warning",
|
|
34
|
+
message: `Resource "${resourceName}" has redundant dependsOn "${depName}" — already referenced in properties`,
|
|
35
|
+
entity: resourceName,
|
|
36
|
+
lexicon: "azure",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return diagnostics;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR011: Missing or Invalid API Version
|
|
3
|
+
*
|
|
4
|
+
* Checks that every ARM resource has a valid apiVersion field.
|
|
5
|
+
* The apiVersion must be a non-empty string matching the date format
|
|
6
|
+
* YYYY-MM-DD (optionally with a suffix like -preview).
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const API_VERSION_PATTERN = /^\d{4}-\d{2}-\d{2}(-preview|-privatepreview)?$/;
|
|
10
|
+
export const azr011 = {
|
|
11
|
+
id: "AZR011",
|
|
12
|
+
description: "Missing or invalid apiVersion — every ARM resource must have a valid apiVersion in YYYY-MM-DD format",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
21
|
+
if (!resource.apiVersion) {
|
|
22
|
+
diagnostics.push({
|
|
23
|
+
checkId: "AZR011",
|
|
24
|
+
severity: "error",
|
|
25
|
+
message: `Resource "${resourceName}" (${resource.type}) is missing apiVersion`,
|
|
26
|
+
entity: resourceName,
|
|
27
|
+
lexicon: "azure",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
else if (typeof resource.apiVersion !== "string" || !API_VERSION_PATTERN.test(resource.apiVersion)) {
|
|
31
|
+
diagnostics.push({
|
|
32
|
+
checkId: "AZR011",
|
|
33
|
+
severity: "error",
|
|
34
|
+
message: `Resource "${resourceName}" (${resource.type}) has invalid apiVersion "${resource.apiVersion}" — expected YYYY-MM-DD format`,
|
|
35
|
+
entity: resourceName,
|
|
36
|
+
lexicon: "azure",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return diagnostics;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR012: Deprecated API Version
|
|
3
|
+
*
|
|
4
|
+
* Warns when a resource uses an apiVersion older than 2023-01-01.
|
|
5
|
+
* Older API versions may lack features, security patches, and
|
|
6
|
+
* could be deprecated by Azure.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const MINIMUM_API_DATE = "2023-01-01";
|
|
10
|
+
export const azr012 = {
|
|
11
|
+
id: "AZR012",
|
|
12
|
+
description: "Deprecated API version — apiVersion older than 2023 may lack features and security patches",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
21
|
+
if (typeof resource.apiVersion !== "string")
|
|
22
|
+
continue;
|
|
23
|
+
// Extract the date portion (YYYY-MM-DD) ignoring any suffix like -preview
|
|
24
|
+
const dateMatch = resource.apiVersion.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
25
|
+
if (!dateMatch)
|
|
26
|
+
continue;
|
|
27
|
+
const apiDate = dateMatch[1];
|
|
28
|
+
if (apiDate < MINIMUM_API_DATE) {
|
|
29
|
+
diagnostics.push({
|
|
30
|
+
checkId: "AZR012",
|
|
31
|
+
severity: "warning",
|
|
32
|
+
message: `Resource "${resourceName}" (${resource.type}) uses outdated apiVersion "${resource.apiVersion}" — consider updating to a version from 2023 or later`,
|
|
33
|
+
entity: resourceName,
|
|
34
|
+
lexicon: "azure",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return diagnostics;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR013: Resource Missing Location
|
|
3
|
+
*
|
|
4
|
+
* Warns when an ARM resource does not have a location property.
|
|
5
|
+
* Most Azure resources require a location. Resources that are
|
|
6
|
+
* inherently global (e.g., Microsoft.Resources/deployments) are excluded.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
/**
|
|
10
|
+
* Resource types that do not require a location property.
|
|
11
|
+
* These are global or subscription-level resources.
|
|
12
|
+
*/
|
|
13
|
+
const LOCATION_EXEMPT_TYPES = new Set([
|
|
14
|
+
"Microsoft.Resources/deployments",
|
|
15
|
+
"Microsoft.Authorization/roleAssignments",
|
|
16
|
+
"Microsoft.Authorization/roleDefinitions",
|
|
17
|
+
"Microsoft.Authorization/policyAssignments",
|
|
18
|
+
"Microsoft.Authorization/policyDefinitions",
|
|
19
|
+
"Microsoft.Authorization/locks",
|
|
20
|
+
"Microsoft.Management/managementGroups",
|
|
21
|
+
"Microsoft.Subscription/aliases",
|
|
22
|
+
]);
|
|
23
|
+
export const azr013 = {
|
|
24
|
+
id: "AZR013",
|
|
25
|
+
description: "Resource missing location — most Azure resources require a location property",
|
|
26
|
+
check(ctx) {
|
|
27
|
+
const diagnostics = [];
|
|
28
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
29
|
+
const template = parseArmTemplate(output);
|
|
30
|
+
if (!template?.resources)
|
|
31
|
+
continue;
|
|
32
|
+
for (const resource of template.resources) {
|
|
33
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
34
|
+
// Skip resource types that don't need location
|
|
35
|
+
if (LOCATION_EXEMPT_TYPES.has(resource.type))
|
|
36
|
+
continue;
|
|
37
|
+
if (resource.location === undefined || resource.location === null || resource.location === "") {
|
|
38
|
+
diagnostics.push({
|
|
39
|
+
checkId: "AZR013",
|
|
40
|
+
severity: "warning",
|
|
41
|
+
message: `Resource "${resourceName}" (${resource.type}) is missing a location property`,
|
|
42
|
+
entity: resourceName,
|
|
43
|
+
lexicon: "azure",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return diagnostics;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR014: Public Blob Access Enabled
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Storage Account has allowBlobPublicAccess set to true
|
|
5
|
+
* or when the property is missing (defaults to true in older API versions).
|
|
6
|
+
* Public blob access can expose data to the internet.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const STORAGE_ACCOUNT_TYPE = "Microsoft.Storage/storageAccounts";
|
|
10
|
+
export const azr014 = {
|
|
11
|
+
id: "AZR014",
|
|
12
|
+
description: "Public blob access enabled on storage account — disable allowBlobPublicAccess to prevent public data exposure",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
if (resource.type !== STORAGE_ACCOUNT_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
// allowBlobPublicAccess defaults to true in older API versions
|
|
25
|
+
// It should be explicitly set to false
|
|
26
|
+
const allowBlobPublicAccess = props.allowBlobPublicAccess;
|
|
27
|
+
if (allowBlobPublicAccess === true) {
|
|
28
|
+
diagnostics.push({
|
|
29
|
+
checkId: "AZR014",
|
|
30
|
+
severity: "warning",
|
|
31
|
+
message: `Storage account "${resourceName}" has allowBlobPublicAccess set to true — disable public blob access`,
|
|
32
|
+
entity: resourceName,
|
|
33
|
+
lexicon: "azure",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
else if (allowBlobPublicAccess === undefined) {
|
|
37
|
+
diagnostics.push({
|
|
38
|
+
checkId: "AZR014",
|
|
39
|
+
severity: "warning",
|
|
40
|
+
message: `Storage account "${resourceName}" does not set allowBlobPublicAccess — explicitly set to false to prevent public access`,
|
|
41
|
+
entity: resourceName,
|
|
42
|
+
lexicon: "azure",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return diagnostics;
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR015: Missing Encryption on Storage Account
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Storage Account does not have encryption configured.
|
|
5
|
+
* Storage accounts should have encryption services enabled for blob,
|
|
6
|
+
* file, table, and queue to protect data at rest.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const STORAGE_ACCOUNT_TYPE = "Microsoft.Storage/storageAccounts";
|
|
10
|
+
export const azr015 = {
|
|
11
|
+
id: "AZR015",
|
|
12
|
+
description: "Missing encryption on storage account — enable encryption services to protect data at rest",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
if (resource.type !== STORAGE_ACCOUNT_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
const encryption = props.encryption;
|
|
25
|
+
if (!encryption) {
|
|
26
|
+
diagnostics.push({
|
|
27
|
+
checkId: "AZR015",
|
|
28
|
+
severity: "warning",
|
|
29
|
+
message: `Storage account "${resourceName}" has no encryption configuration — enable encryption services for blob, file, table, and queue`,
|
|
30
|
+
entity: resourceName,
|
|
31
|
+
lexicon: "azure",
|
|
32
|
+
});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
// Check that encryption services are defined
|
|
36
|
+
const services = encryption.services;
|
|
37
|
+
if (!services) {
|
|
38
|
+
diagnostics.push({
|
|
39
|
+
checkId: "AZR015",
|
|
40
|
+
severity: "warning",
|
|
41
|
+
message: `Storage account "${resourceName}" has encryption configured but no encryption services specified — enable blob, file, table, and queue encryption`,
|
|
42
|
+
entity: resourceName,
|
|
43
|
+
lexicon: "azure",
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// Check for blob and file at minimum
|
|
48
|
+
const missingServices = [];
|
|
49
|
+
if (!services.blob)
|
|
50
|
+
missingServices.push("blob");
|
|
51
|
+
if (!services.file)
|
|
52
|
+
missingServices.push("file");
|
|
53
|
+
if (missingServices.length > 0) {
|
|
54
|
+
diagnostics.push({
|
|
55
|
+
checkId: "AZR015",
|
|
56
|
+
severity: "warning",
|
|
57
|
+
message: `Storage account "${resourceName}" is missing encryption for: ${missingServices.join(", ")}`,
|
|
58
|
+
entity: resourceName,
|
|
59
|
+
lexicon: "azure",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return diagnostics;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR016: Key Vault soft-delete not enabled
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Key Vault does not have soft-delete enabled.
|
|
5
|
+
* Soft delete is required to protect against accidental deletion
|
|
6
|
+
* of keys, secrets, and certificates.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const KEY_VAULT_TYPE = "Microsoft.KeyVault/vaults";
|
|
10
|
+
export const azr016 = {
|
|
11
|
+
id: "AZR016",
|
|
12
|
+
description: "Key Vault soft-delete not enabled — enable to protect against accidental deletion",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
if (resource.type !== KEY_VAULT_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
if (props.enableSoftDelete !== true) {
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
checkId: "AZR016",
|
|
27
|
+
severity: "warning",
|
|
28
|
+
message: `Key Vault "${resourceName}" does not have soft-delete enabled — set enableSoftDelete to true`,
|
|
29
|
+
entity: resourceName,
|
|
30
|
+
lexicon: "azure",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return diagnostics;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR017: Key Vault purge protection not enabled
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Key Vault does not have purge protection enabled.
|
|
5
|
+
* Purge protection prevents permanent deletion of soft-deleted vaults
|
|
6
|
+
* and vault objects during the retention period.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const KEY_VAULT_TYPE = "Microsoft.KeyVault/vaults";
|
|
10
|
+
export const azr017 = {
|
|
11
|
+
id: "AZR017",
|
|
12
|
+
description: "Key Vault purge protection not enabled — enable to prevent permanent deletion during retention period",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
if (resource.type !== KEY_VAULT_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
if (props.enablePurgeProtection !== true) {
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
checkId: "AZR017",
|
|
27
|
+
severity: "warning",
|
|
28
|
+
message: `Key Vault "${resourceName}" does not have purge protection enabled — set enablePurgeProtection to true`,
|
|
29
|
+
entity: resourceName,
|
|
30
|
+
lexicon: "azure",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return diagnostics;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR018: SQL Server missing auditing
|
|
3
|
+
*
|
|
4
|
+
* Warns when a SQL Server does not have auditing configured.
|
|
5
|
+
* Azure SQL auditing tracks database events and writes them
|
|
6
|
+
* to an audit log, helping meet compliance requirements.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const SQL_SERVER_TYPE = "Microsoft.Sql/servers";
|
|
10
|
+
const AUDIT_TYPE = "Microsoft.Sql/servers/auditingSettings";
|
|
11
|
+
export const azr018 = {
|
|
12
|
+
id: "AZR018",
|
|
13
|
+
description: "SQL Server missing auditing — enable auditing for compliance and threat detection",
|
|
14
|
+
check(ctx) {
|
|
15
|
+
const diagnostics = [];
|
|
16
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
17
|
+
const template = parseArmTemplate(output);
|
|
18
|
+
if (!template?.resources)
|
|
19
|
+
continue;
|
|
20
|
+
const sqlServers = template.resources.filter((r) => r.type === SQL_SERVER_TYPE);
|
|
21
|
+
const auditSettings = template.resources.filter((r) => r.type === AUDIT_TYPE);
|
|
22
|
+
for (const server of sqlServers) {
|
|
23
|
+
const serverName = typeof server.name === "string" ? server.name : String(server.name);
|
|
24
|
+
// Check for a matching auditing settings resource
|
|
25
|
+
const hasAudit = auditSettings.some((a) => {
|
|
26
|
+
const auditName = typeof a.name === "string" ? a.name : String(a.name);
|
|
27
|
+
return auditName.startsWith(`${serverName}/`);
|
|
28
|
+
});
|
|
29
|
+
if (!hasAudit) {
|
|
30
|
+
diagnostics.push({
|
|
31
|
+
checkId: "AZR018",
|
|
32
|
+
severity: "warning",
|
|
33
|
+
message: `SQL Server "${serverName}" does not have auditing configured — add a Microsoft.Sql/servers/auditingSettings resource`,
|
|
34
|
+
entity: serverName,
|
|
35
|
+
lexicon: "azure",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return diagnostics;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR019: SQL Server missing Transparent Data Encryption (TDE)
|
|
3
|
+
*
|
|
4
|
+
* Warns when a SQL Server database does not have TDE configured.
|
|
5
|
+
* TDE provides encryption at rest for the entire database, backups,
|
|
6
|
+
* and transaction log files.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const SQL_DB_TYPE = "Microsoft.Sql/servers/databases";
|
|
10
|
+
const TDE_TYPE = "Microsoft.Sql/servers/databases/transparentDataEncryption";
|
|
11
|
+
export const azr019 = {
|
|
12
|
+
id: "AZR019",
|
|
13
|
+
description: "SQL Server database missing TDE — enable Transparent Data Encryption to protect data at rest",
|
|
14
|
+
check(ctx) {
|
|
15
|
+
const diagnostics = [];
|
|
16
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
17
|
+
const template = parseArmTemplate(output);
|
|
18
|
+
if (!template?.resources)
|
|
19
|
+
continue;
|
|
20
|
+
const databases = template.resources.filter((r) => r.type === SQL_DB_TYPE);
|
|
21
|
+
const tdeSettings = template.resources.filter((r) => r.type === TDE_TYPE);
|
|
22
|
+
for (const db of databases) {
|
|
23
|
+
const dbName = typeof db.name === "string" ? db.name : String(db.name);
|
|
24
|
+
const hasTde = tdeSettings.some((t) => {
|
|
25
|
+
const tdeName = typeof t.name === "string" ? t.name : String(t.name);
|
|
26
|
+
return tdeName.startsWith(`${dbName}/`);
|
|
27
|
+
});
|
|
28
|
+
if (!hasTde) {
|
|
29
|
+
diagnostics.push({
|
|
30
|
+
checkId: "AZR019",
|
|
31
|
+
severity: "warning",
|
|
32
|
+
message: `SQL database "${dbName}" does not have TDE configured — add transparentDataEncryption resource`,
|
|
33
|
+
entity: dbName,
|
|
34
|
+
lexicon: "azure",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return diagnostics;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR020: App Service missing managed identity
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Web App does not have a managed identity configured.
|
|
5
|
+
* Managed identity eliminates the need for credentials in code
|
|
6
|
+
* and enables secure access to Azure services.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const WEB_APP_TYPE = "Microsoft.Web/sites";
|
|
10
|
+
export const azr020 = {
|
|
11
|
+
id: "AZR020",
|
|
12
|
+
description: "App Service missing managed identity — enable SystemAssigned or UserAssigned identity",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
if (resource.type !== WEB_APP_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const identity = resource.identity;
|
|
24
|
+
if (!identity || !identity.type) {
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
checkId: "AZR020",
|
|
27
|
+
severity: "warning",
|
|
28
|
+
message: `App Service "${resourceName}" does not have managed identity configured — set identity.type to "SystemAssigned" or "UserAssigned"`,
|
|
29
|
+
entity: resourceName,
|
|
30
|
+
lexicon: "azure",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return diagnostics;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR021: App Service missing HTTPS-only
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Web App does not enforce HTTPS-only traffic.
|
|
5
|
+
* HTTP traffic can be intercepted and exposes sensitive data.
|
|
6
|
+
*/
|
|
7
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
8
|
+
const WEB_APP_TYPE = "Microsoft.Web/sites";
|
|
9
|
+
export const azr021 = {
|
|
10
|
+
id: "AZR021",
|
|
11
|
+
description: "App Service missing HTTPS-only — set httpsOnly to true to enforce encrypted traffic",
|
|
12
|
+
check(ctx) {
|
|
13
|
+
const diagnostics = [];
|
|
14
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
15
|
+
const template = parseArmTemplate(output);
|
|
16
|
+
if (!template?.resources)
|
|
17
|
+
continue;
|
|
18
|
+
for (const resource of template.resources) {
|
|
19
|
+
if (resource.type !== WEB_APP_TYPE)
|
|
20
|
+
continue;
|
|
21
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
22
|
+
const props = resource.properties ?? {};
|
|
23
|
+
if (props.httpsOnly !== true) {
|
|
24
|
+
diagnostics.push({
|
|
25
|
+
checkId: "AZR021",
|
|
26
|
+
severity: "warning",
|
|
27
|
+
message: `App Service "${resourceName}" does not enforce HTTPS-only — set httpsOnly to true`,
|
|
28
|
+
entity: resourceName,
|
|
29
|
+
lexicon: "azure",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return diagnostics;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR022: App Service missing minimum TLS 1.2
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Web App does not enforce a minimum TLS version of 1.2.
|
|
5
|
+
* Older TLS versions (1.0, 1.1) have known vulnerabilities and should
|
|
6
|
+
* not be used.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const WEB_APP_TYPE = "Microsoft.Web/sites";
|
|
10
|
+
export const azr022 = {
|
|
11
|
+
id: "AZR022",
|
|
12
|
+
description: "App Service missing minimum TLS 1.2 — set minTlsVersion in siteConfig to enforce TLS 1.2+",
|
|
13
|
+
check(ctx) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
16
|
+
const template = parseArmTemplate(output);
|
|
17
|
+
if (!template?.resources)
|
|
18
|
+
continue;
|
|
19
|
+
for (const resource of template.resources) {
|
|
20
|
+
if (resource.type !== WEB_APP_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
const siteConfig = props.siteConfig;
|
|
25
|
+
const minTlsVersion = siteConfig?.minTlsVersion;
|
|
26
|
+
if (minTlsVersion !== "1.2" && minTlsVersion !== "1.3") {
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
checkId: "AZR022",
|
|
29
|
+
severity: "warning",
|
|
30
|
+
message: `App Service "${resourceName}" does not enforce minimum TLS 1.2 — set siteConfig.minTlsVersion to "1.2"`,
|
|
31
|
+
entity: resourceName,
|
|
32
|
+
lexicon: "azure",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return diagnostics;
|
|
38
|
+
},
|
|
39
|
+
};
|