@intentius/chant-lexicon-azure 0.124.31 → 0.124.32
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.js +16598 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/governance.js +144 -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 +2 -2
- 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.js +94 -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/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 +472 -0
- package/dist/pseudo.js +40 -0
- package/dist/serializer.js +457 -0
- package/dist/spec/api-versions.js +130 -0
- package/dist/spec/fetch.js +196 -0
- package/dist/spec/parse.js +308 -0
- package/dist/taggable.js +34 -0
- package/dist/validate-cli.js +16 -0
- package/dist/validate.js +53 -0
- package/package.json +8 -8
- package/src/codegen/package.ts +2 -2
- package/src/deploy-scopes.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/plugin.ts +3 -2
- package/src/serializer.ts +2 -2
- package/src/taggable.ts +2 -2
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARM template function intrinsics.
|
|
3
|
+
*
|
|
4
|
+
* Each intrinsic implements INTRINSIC_MARKER + toJSON() for serialization
|
|
5
|
+
* to ARM template bracket expressions: "[resourceId(...)]", "[concat(...)]", etc.
|
|
6
|
+
*/
|
|
7
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
8
|
+
import { INTRINSIC_MARKER, resolveIntrinsicValue } from "@intentius/chant/intrinsic";
|
|
9
|
+
/**
|
|
10
|
+
* resourceId(resourceType, name1, name2, ...) — returns a resource ID string.
|
|
11
|
+
*/
|
|
12
|
+
export class ResourceIdIntrinsic {
|
|
13
|
+
constructor(resourceType, ...names) {
|
|
14
|
+
this[_a] = true;
|
|
15
|
+
this.resourceType = resourceType;
|
|
16
|
+
this.names = names;
|
|
17
|
+
}
|
|
18
|
+
toJSON() {
|
|
19
|
+
const args = [
|
|
20
|
+
`'${this.resourceType}'`,
|
|
21
|
+
...this.names.map((n) => serializeArg(n)),
|
|
22
|
+
].join(", ");
|
|
23
|
+
return `[resourceId(${args})]`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
_a = INTRINSIC_MARKER;
|
|
27
|
+
export function ResourceId(resourceType, ...names) {
|
|
28
|
+
return new ResourceIdIntrinsic(resourceType, ...names);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* reference(name, apiVersion?) — returns the runtime state of a resource.
|
|
32
|
+
*/
|
|
33
|
+
export class ReferenceIntrinsic {
|
|
34
|
+
constructor(name, apiVersion) {
|
|
35
|
+
this[_b] = true;
|
|
36
|
+
this.name = name;
|
|
37
|
+
this.apiVersion = apiVersion;
|
|
38
|
+
}
|
|
39
|
+
toJSON() {
|
|
40
|
+
if (this.apiVersion) {
|
|
41
|
+
return `[reference('${this.name}', '${this.apiVersion}')]`;
|
|
42
|
+
}
|
|
43
|
+
return `[reference('${this.name}')]`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
_b = INTRINSIC_MARKER;
|
|
47
|
+
export function Reference(name, apiVersion) {
|
|
48
|
+
return new ReferenceIntrinsic(name, apiVersion);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* concat(value1, value2, ...) — concatenates strings.
|
|
52
|
+
*/
|
|
53
|
+
export class ConcatIntrinsic {
|
|
54
|
+
constructor(...values) {
|
|
55
|
+
this[_c] = true;
|
|
56
|
+
this.values = values;
|
|
57
|
+
}
|
|
58
|
+
toJSON() {
|
|
59
|
+
const args = this.values.map((v) => serializeArg(v)).join(", ");
|
|
60
|
+
return `[concat(${args})]`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
_c = INTRINSIC_MARKER;
|
|
64
|
+
export function Concat(...values) {
|
|
65
|
+
return new ConcatIntrinsic(...values);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* resourceGroup() — returns the current resource group object.
|
|
69
|
+
*/
|
|
70
|
+
export class ResourceGroupIntrinsic {
|
|
71
|
+
constructor() {
|
|
72
|
+
this[_d] = true;
|
|
73
|
+
}
|
|
74
|
+
toJSON() {
|
|
75
|
+
return "[resourceGroup()]";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
_d = INTRINSIC_MARKER;
|
|
79
|
+
export function ResourceGroup() {
|
|
80
|
+
return new ResourceGroupIntrinsic();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* subscription() — returns the current subscription object.
|
|
84
|
+
*/
|
|
85
|
+
export class SubscriptionIntrinsic {
|
|
86
|
+
constructor() {
|
|
87
|
+
this[_e] = true;
|
|
88
|
+
}
|
|
89
|
+
toJSON() {
|
|
90
|
+
return "[subscription()]";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
_e = INTRINSIC_MARKER;
|
|
94
|
+
export function Subscription() {
|
|
95
|
+
return new SubscriptionIntrinsic();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* uniqueString(value1, ...) — generates a deterministic hash string.
|
|
99
|
+
*/
|
|
100
|
+
export class UniqueStringIntrinsic {
|
|
101
|
+
constructor(...values) {
|
|
102
|
+
this[_f] = true;
|
|
103
|
+
this.values = values;
|
|
104
|
+
}
|
|
105
|
+
toJSON() {
|
|
106
|
+
const args = this.values.map((v) => serializeArg(v)).join(", ");
|
|
107
|
+
return `[uniqueString(${args})]`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
_f = INTRINSIC_MARKER;
|
|
111
|
+
export function UniqueString(...values) {
|
|
112
|
+
return new UniqueStringIntrinsic(...values);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* format(formatString, arg1, arg2, ...) — formats a string.
|
|
116
|
+
*/
|
|
117
|
+
export class FormatIntrinsic {
|
|
118
|
+
constructor(fmt, ...args) {
|
|
119
|
+
this[_g] = true;
|
|
120
|
+
this.fmt = fmt;
|
|
121
|
+
this.args = args;
|
|
122
|
+
}
|
|
123
|
+
toJSON() {
|
|
124
|
+
const allArgs = [`'${this.fmt}'`, ...this.args.map((a) => serializeArg(a))].join(", ");
|
|
125
|
+
return `[format(${allArgs})]`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
_g = INTRINSIC_MARKER;
|
|
129
|
+
export function Format(fmt, ...args) {
|
|
130
|
+
return new FormatIntrinsic(fmt, ...args);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* if(condition, trueValue, falseValue) — conditional expression.
|
|
134
|
+
*/
|
|
135
|
+
export class IfIntrinsic {
|
|
136
|
+
constructor(condition, trueValue, falseValue) {
|
|
137
|
+
this[_h] = true;
|
|
138
|
+
this.condition = condition;
|
|
139
|
+
this.trueValue = trueValue;
|
|
140
|
+
this.falseValue = falseValue;
|
|
141
|
+
}
|
|
142
|
+
toJSON() {
|
|
143
|
+
return `[if('${this.condition}', ${serializeArg(this.trueValue)}, ${serializeArg(this.falseValue)})]`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
_h = INTRINSIC_MARKER;
|
|
147
|
+
export function If(condition, trueValue, falseValue) {
|
|
148
|
+
return new IfIntrinsic(condition, trueValue, falseValue);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* listKeys(resourceId, apiVersion) — lists access keys for a resource.
|
|
152
|
+
*/
|
|
153
|
+
export class ListKeysIntrinsic {
|
|
154
|
+
constructor(resourceId, apiVersion) {
|
|
155
|
+
this[_j] = true;
|
|
156
|
+
this.resourceId = resourceId;
|
|
157
|
+
this.apiVersion = apiVersion;
|
|
158
|
+
}
|
|
159
|
+
toJSON() {
|
|
160
|
+
return `[listKeys(${serializeArg(this.resourceId)}, '${this.apiVersion}')]`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
_j = INTRINSIC_MARKER;
|
|
164
|
+
export function ListKeys(resourceId, apiVersion) {
|
|
165
|
+
return new ListKeysIntrinsic(resourceId, apiVersion);
|
|
166
|
+
}
|
|
167
|
+
// --- Helpers ---
|
|
168
|
+
function serializeArg(value) {
|
|
169
|
+
if (typeof value === "string")
|
|
170
|
+
return `'${value}'`;
|
|
171
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
172
|
+
return String(value);
|
|
173
|
+
const resolved = resolveIntrinsicValue(value);
|
|
174
|
+
if (typeof resolved === "string" && resolved.startsWith("[") && resolved.endsWith("]")) {
|
|
175
|
+
// Nested ARM expression — strip outer brackets
|
|
176
|
+
return resolved.slice(1, -1);
|
|
177
|
+
}
|
|
178
|
+
return JSON.stringify(resolved);
|
|
179
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The azure lexicon's `chant audit` catalog — metadata for the AZR* ARM
|
|
3
|
+
* post-synth rules. Contributed via `azurePlugin.auditCatalog()` (#687).
|
|
4
|
+
*/
|
|
5
|
+
import { auditRule, applyLineage } from "@intentius/chant/audit/catalog";
|
|
6
|
+
import { azureAuditLineage } from "./audit-lineage.js";
|
|
7
|
+
const AZ_SEC = { name: "Microsoft Cloud Security Benchmark", url: "https://learn.microsoft.com/en-us/security/benchmark/azure/" };
|
|
8
|
+
export const azureAuditCatalog = {
|
|
9
|
+
AZR010: auditRule("AZR010", "report-only", "guidance", "Redundant dependsOn", "Remove dependsOn already implied by reference()/resourceId().", { category: "best-practice" }),
|
|
10
|
+
AZR011: auditRule("AZR011", "merge-worthy", "guidance", "Missing or invalid apiVersion", "Set a valid YYYY-MM-DD apiVersion on every resource.", { category: "correctness" }),
|
|
11
|
+
AZR012: auditRule("AZR012", "report-only", "guidance", "Deprecated API version", "Move to a current apiVersion.", { category: "best-practice" }),
|
|
12
|
+
AZR013: auditRule("AZR013", "merge-worthy", "guidance", "Resource missing location", "Add the required location property.", { category: "correctness" }),
|
|
13
|
+
AZR014: auditRule("AZR014", "merge-worthy", "guidance", "Storage account allows public blob access", "Set allowBlobPublicAccess to false.", { authority: [AZ_SEC] }),
|
|
14
|
+
AZR015: auditRule("AZR015", "merge-worthy", "guidance", "Storage account missing encryption", "Enable encryption services for data at rest.", { authority: [AZ_SEC] }),
|
|
15
|
+
AZR016: auditRule("AZR016", "report-only", "guidance", "Key Vault soft-delete not enabled", "Enable soft-delete.", { category: "best-practice" }),
|
|
16
|
+
AZR017: auditRule("AZR017", "report-only", "guidance", "Key Vault purge protection not enabled", "Enable purge protection.", { category: "best-practice" }),
|
|
17
|
+
AZR018: auditRule("AZR018", "report-only", "guidance", "SQL Server missing auditing", "Enable auditing for compliance and threat detection.", { category: "best-practice" }),
|
|
18
|
+
AZR019: auditRule("AZR019", "merge-worthy", "guidance", "SQL database missing TDE", "Enable Transparent Data Encryption.", { authority: [AZ_SEC] }),
|
|
19
|
+
AZR020: auditRule("AZR020", "report-only", "guidance", "App Service missing managed identity", "Enable a system- or user-assigned identity.", { category: "best-practice" }),
|
|
20
|
+
AZR021: auditRule("AZR021", "merge-worthy", "guidance", "App Service not HTTPS-only", "Set httpsOnly to true.", { authority: [AZ_SEC] }),
|
|
21
|
+
AZR022: auditRule("AZR022", "merge-worthy", "guidance", "App Service min TLS below 1.2", "Set minTlsVersion to 1.2+.", { authority: [AZ_SEC] }),
|
|
22
|
+
AZR023: auditRule("AZR023", "report-only", "guidance", "VM not using a managed disk", "Use a managed disk.", { category: "best-practice" }),
|
|
23
|
+
AZR024: auditRule("AZR024", "report-only", "guidance", "VM missing boot diagnostics", "Enable boot diagnostics.", { category: "best-practice" }),
|
|
24
|
+
AZR025: auditRule("AZR025", "report-only", "guidance", "AKS cluster missing RBAC", "Enable Kubernetes RBAC.", { category: "best-practice" }),
|
|
25
|
+
AZR026: auditRule("AZR026", "report-only", "guidance", "AKS cluster missing network policy", "Configure a networkPolicy.", { category: "best-practice" }),
|
|
26
|
+
AZR027: auditRule("AZR027", "merge-worthy", "guidance", "Container Registry admin user enabled", "Disable the admin user; use Azure AD / service principals.", { authority: [AZ_SEC] }),
|
|
27
|
+
AZR028: auditRule("AZR028", "report-only", "guidance", "Network interface missing NSG", "Associate an NSG to control traffic.", { category: "best-practice" }),
|
|
28
|
+
AZR029: auditRule("AZR029", "merge-worthy", "guidance", "Managed disk missing encryption", "Enable encryption for data at rest.", { authority: [AZ_SEC] }),
|
|
29
|
+
AZR030: auditRule("AZR030", "merge-worthy", "guidance", "Resource at unsupported template scope", "Move the resource to a project deployed at a scope its schema supports.", { category: "correctness" }),
|
|
30
|
+
// governance posture regressions.
|
|
31
|
+
AZR031: auditRule("AZR031", "merge-worthy", "guidance", "Policy assignment not enforced (DoNotEnforce)", "Set enforcementMode to Default so the assigned guardrail applies.", { authority: [AZ_SEC] }),
|
|
32
|
+
AZR032: auditRule("AZR032", "merge-worthy", "guidance", "Custom policy definition assigned nowhere", "Pair the policy definition with a policy assignment — unassigned it enforces nothing.", { authority: [AZ_SEC] }),
|
|
33
|
+
AZR033: auditRule("AZR033", "merge-worthy", "guidance", "Subscriptions may leave the tenant", "Set blockSubscriptionsLeavingTenant: true on the Microsoft.Subscription/policies resource.", { authority: [AZ_SEC] }),
|
|
34
|
+
};
|
|
35
|
+
// Prior art credits live beside the rules in ./audit-lineage.ts (see core audit/prior-art.ts).
|
|
36
|
+
applyLineage(azureAuditCatalog, azureAuditLineage);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const azureAuditLineage = {
|
|
2
|
+
AZR010: [
|
|
3
|
+
{ tool: "bicep-linter", rule: "no-unnecessary-dependson", url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/linter-rule-no-unnecessary-dependson", relation: "equivalent" },
|
|
4
|
+
],
|
|
5
|
+
AZR011: [
|
|
6
|
+
{ tool: "arm-ttk", rule: "apiVersions-Should-Be-Recent", url: "https://github.com/Azure/arm-ttk/blob/master/arm-ttk/testcases/deploymentTemplate/apiVersions-Should-Be-Recent.test.ps1", relation: "overlaps" },
|
|
7
|
+
],
|
|
8
|
+
AZR012: [
|
|
9
|
+
{ tool: "arm-ttk", rule: "apiVersions-Should-Be-Recent", url: "https://github.com/Azure/arm-ttk/blob/master/arm-ttk/testcases/deploymentTemplate/apiVersions-Should-Be-Recent.test.ps1", relation: "overlaps" },
|
|
10
|
+
{ tool: "bicep-linter", rule: "use-recent-api-versions", url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/linter-rule-use-recent-api-versions", relation: "overlaps" },
|
|
11
|
+
],
|
|
12
|
+
AZR014: [
|
|
13
|
+
{ tool: "psrule-azure", rule: "Azure.Storage.BlobPublicAccess", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Storage.BlobPublicAccess/", relation: "equivalent" },
|
|
14
|
+
{ tool: "checkov", rule: "CKV_AZURE_59", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
15
|
+
],
|
|
16
|
+
AZR016: [
|
|
17
|
+
{ tool: "psrule-azure", rule: "Azure.KeyVault.SoftDelete", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.KeyVault.SoftDelete/", relation: "equivalent" },
|
|
18
|
+
{ tool: "checkov", rule: "CKV_AZURE_111", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
19
|
+
{ tool: "kics", rule: "Key Vault Not Recoverable", url: "https://docs.kics.io/latest/queries/azureresourcemanager-queries/azure/7c25f361-7c66-44bf-9b69-022acd5eb4bd/", relation: "overlaps" },
|
|
20
|
+
],
|
|
21
|
+
AZR017: [
|
|
22
|
+
{ tool: "psrule-azure", rule: "Azure.KeyVault.PurgeProtect", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.KeyVault.PurgeProtect/", relation: "equivalent" },
|
|
23
|
+
{ tool: "checkov", rule: "CKV_AZURE_110", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
24
|
+
{ tool: "kics", rule: "Key Vault Not Recoverable", url: "https://docs.kics.io/latest/queries/azureresourcemanager-queries/azure/7c25f361-7c66-44bf-9b69-022acd5eb4bd/", relation: "overlaps" },
|
|
25
|
+
],
|
|
26
|
+
AZR018: [
|
|
27
|
+
{ tool: "psrule-azure", rule: "Azure.SQL.Auditing", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.SQL.Auditing/", relation: "equivalent" },
|
|
28
|
+
{ tool: "checkov", rule: "CKV_AZURE_23", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
29
|
+
{ tool: "kics", rule: "SQL Server Database Without Auditing", url: "https://docs.kics.io/latest/queries/azureresourcemanager-queries/azure/e055285c-bc01-48b4-8aa5-8a54acdd29df/", relation: "equivalent" },
|
|
30
|
+
],
|
|
31
|
+
AZR019: [
|
|
32
|
+
{ tool: "psrule-azure", rule: "Azure.SQL.TDE", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.SQL.TDE/", relation: "equivalent" },
|
|
33
|
+
],
|
|
34
|
+
AZR020: [
|
|
35
|
+
{ tool: "psrule-azure", rule: "Azure.AppService.ManagedIdentity", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AppService.ManagedIdentity/", relation: "equivalent" },
|
|
36
|
+
{ tool: "checkov", rule: "CKV_AZURE_71", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
37
|
+
],
|
|
38
|
+
AZR021: [
|
|
39
|
+
{ tool: "psrule-azure", rule: "Azure.AppService.UseHTTPS", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AppService.UseHTTPS/", relation: "equivalent" },
|
|
40
|
+
{ tool: "checkov", rule: "CKV_AZURE_14", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
41
|
+
{ tool: "kics", rule: "Website Not Forcing HTTPS", url: "https://docs.kics.io/latest/queries/azureresourcemanager-queries/azure/488847ff-6031-487c-bf42-98fd6ac5c9a0/", relation: "equivalent" },
|
|
42
|
+
],
|
|
43
|
+
AZR022: [
|
|
44
|
+
{ tool: "psrule-azure", rule: "Azure.AppService.MinTLS", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AppService.MinTLS/", relation: "equivalent" },
|
|
45
|
+
{ tool: "checkov", rule: "CKV_AZURE_15", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
46
|
+
{ tool: "kics", rule: "Web App Not Using TLS Last Version", url: "https://docs.kics.io/latest/queries/azureresourcemanager-queries/azure/b5c851d5-00f1-43dc-a8de-3218fd6f71be/", relation: "equivalent" },
|
|
47
|
+
],
|
|
48
|
+
AZR023: [
|
|
49
|
+
{ tool: "psrule-azure", rule: "Azure.VM.UseManagedDisks", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.VM.UseManagedDisks/", relation: "equivalent" },
|
|
50
|
+
{ tool: "checkov", rule: "CKV_AZURE_92", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
51
|
+
],
|
|
52
|
+
AZR025: [
|
|
53
|
+
{ tool: "psrule-azure", rule: "Azure.AKS.UseRBAC", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AKS.UseRBAC/", relation: "equivalent" },
|
|
54
|
+
{ tool: "checkov", rule: "CKV_AZURE_5", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
55
|
+
{ tool: "kics", rule: "AKS Cluster RBAC Disabled", url: "https://docs.kics.io/latest/queries/azureresourcemanager-queries/azure/9307a2ed-35c2-413d-94de-a1a0682c2158/", relation: "equivalent" },
|
|
56
|
+
],
|
|
57
|
+
AZR026: [
|
|
58
|
+
{ tool: "psrule-azure", rule: "Azure.AKS.NetworkPolicy", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AKS.NetworkPolicy/", relation: "equivalent" },
|
|
59
|
+
{ tool: "checkov", rule: "CKV_AZURE_7", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
60
|
+
{ tool: "kics", rule: "AKS Cluster Network Policy Not Configured", url: "https://docs.kics.io/latest/queries/azureresourcemanager-queries/azure/25c0228e-4444-459b-a2df-93c7df40b7ed/", relation: "equivalent" },
|
|
61
|
+
],
|
|
62
|
+
AZR027: [
|
|
63
|
+
{ tool: "psrule-azure", rule: "Azure.ACR.AdminUser", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.ACR.AdminUser/", relation: "equivalent" },
|
|
64
|
+
{ tool: "checkov", rule: "CKV_AZURE_137", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
65
|
+
],
|
|
66
|
+
AZR029: [
|
|
67
|
+
{ tool: "checkov", rule: "CKV_AZURE_2", url: "https://www.checkov.io/5.Policy%20Index/arm.html", relation: "equivalent" },
|
|
68
|
+
{ tool: "kics", rule: "Azure Managed Disk Without Encryption", url: "https://docs.kics.io/latest/queries/azureresourcemanager-queries/azure/350f3955-b5be-436f-afaa-3d2be2fa6cdd/", relation: "equivalent" },
|
|
69
|
+
{ tool: "psrule-azure", rule: "Azure.VM.ADE", url: "https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.VM.ADE/", relation: "overlaps" },
|
|
70
|
+
],
|
|
71
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
* - [reference('name')] -> extracts 'name'
|
|
54
|
+
* - [reference('name').prop] -> extracts 'name'
|
|
55
|
+
*/
|
|
56
|
+
export function extractRefsFromExpression(expr) {
|
|
57
|
+
const refs = [];
|
|
58
|
+
// Match resourceId('...', 'name') — extract the last quoted argument (the resource name)
|
|
59
|
+
const resourceIdPattern = /resourceId\s*\([^)]*,\s*'([^']+)'\s*\)/g;
|
|
60
|
+
let match;
|
|
61
|
+
while ((match = resourceIdPattern.exec(expr)) !== null) {
|
|
62
|
+
refs.push(match[1]);
|
|
63
|
+
}
|
|
64
|
+
// Match reference('name') — extract the name
|
|
65
|
+
const referencePattern = /reference\s*\(\s*'([^']+)'\s*\)/g;
|
|
66
|
+
while ((match = referencePattern.exec(expr)) !== null) {
|
|
67
|
+
refs.push(match[1]);
|
|
68
|
+
}
|
|
69
|
+
return refs;
|
|
70
|
+
}
|
|
71
|
+
function walkArmValue(value, refs) {
|
|
72
|
+
if (value === null || value === undefined)
|
|
73
|
+
return;
|
|
74
|
+
if (typeof value === "string") {
|
|
75
|
+
if (isBracketExpression(value)) {
|
|
76
|
+
for (const ref of extractRefsFromExpression(value)) {
|
|
77
|
+
refs.add(ref);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(value)) {
|
|
83
|
+
for (const item of value) {
|
|
84
|
+
walkArmValue(item, refs);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (typeof value === "object") {
|
|
89
|
+
const obj = value;
|
|
90
|
+
for (const val of Object.values(obj)) {
|
|
91
|
+
walkArmValue(val, refs);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -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
|
+
};
|