@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,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR023: VM missing managed disk
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Virtual Machine's OS disk does not use managed disks.
|
|
5
|
+
* Managed disks provide better reliability, scalability, and
|
|
6
|
+
* simplified management compared to unmanaged (VHD-based) disks.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const VM_TYPE = "Microsoft.Compute/virtualMachines";
|
|
10
|
+
export const azr023 = {
|
|
11
|
+
id: "AZR023",
|
|
12
|
+
description: "VM missing managed disk — use managed disks for better reliability and management",
|
|
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 !== VM_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
const storageProfile = props.storageProfile;
|
|
25
|
+
const osDisk = storageProfile?.osDisk;
|
|
26
|
+
if (!osDisk?.managedDisk) {
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
checkId: "AZR023",
|
|
29
|
+
severity: "warning",
|
|
30
|
+
message: `VM "${resourceName}" OS disk does not use managed disks — add managedDisk configuration to storageProfile.osDisk`,
|
|
31
|
+
entity: resourceName,
|
|
32
|
+
lexicon: "azure",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return diagnostics;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR024: VM missing boot diagnostics
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Virtual Machine does not have boot diagnostics enabled.
|
|
5
|
+
* Boot diagnostics helps troubleshoot VM startup failures by capturing
|
|
6
|
+
* serial console output and screenshots.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const VM_TYPE = "Microsoft.Compute/virtualMachines";
|
|
10
|
+
export const azr024 = {
|
|
11
|
+
id: "AZR024",
|
|
12
|
+
description: "VM missing boot diagnostics — enable for troubleshooting startup failures",
|
|
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 !== VM_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
const diagnosticsProfile = props.diagnosticsProfile;
|
|
25
|
+
const bootDiagnostics = diagnosticsProfile?.bootDiagnostics;
|
|
26
|
+
if (!bootDiagnostics || bootDiagnostics.enabled !== true) {
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
checkId: "AZR024",
|
|
29
|
+
severity: "warning",
|
|
30
|
+
message: `VM "${resourceName}" does not have boot diagnostics enabled — set diagnosticsProfile.bootDiagnostics.enabled to true`,
|
|
31
|
+
entity: resourceName,
|
|
32
|
+
lexicon: "azure",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return diagnostics;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR025: AKS missing RBAC
|
|
3
|
+
*
|
|
4
|
+
* Warns when an AKS cluster does not have RBAC enabled.
|
|
5
|
+
* RBAC provides fine-grained access control for Kubernetes
|
|
6
|
+
* resources within the cluster.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const AKS_TYPE = "Microsoft.ContainerService/managedClusters";
|
|
10
|
+
export const azr025 = {
|
|
11
|
+
id: "AZR025",
|
|
12
|
+
description: "AKS cluster missing RBAC — enable Kubernetes RBAC for access control",
|
|
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 !== AKS_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
if (props.enableRBAC !== true) {
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
checkId: "AZR025",
|
|
27
|
+
severity: "warning",
|
|
28
|
+
message: `AKS cluster "${resourceName}" does not have RBAC enabled — set enableRBAC to true`,
|
|
29
|
+
entity: resourceName,
|
|
30
|
+
lexicon: "azure",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return diagnostics;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR026: AKS missing network policy
|
|
3
|
+
*
|
|
4
|
+
* Warns when an AKS cluster does not have a network policy configured.
|
|
5
|
+
* Network policies control pod-to-pod traffic and are essential for
|
|
6
|
+
* microsegmentation and defense in depth.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const AKS_TYPE = "Microsoft.ContainerService/managedClusters";
|
|
10
|
+
export const azr026 = {
|
|
11
|
+
id: "AZR026",
|
|
12
|
+
description: "AKS cluster missing network policy — configure networkPolicy for pod-to-pod traffic control",
|
|
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 !== AKS_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
const networkProfile = props.networkProfile;
|
|
25
|
+
if (!networkProfile?.networkPolicy) {
|
|
26
|
+
diagnostics.push({
|
|
27
|
+
checkId: "AZR026",
|
|
28
|
+
severity: "warning",
|
|
29
|
+
message: `AKS cluster "${resourceName}" does not have a network policy configured — set networkProfile.networkPolicy to "azure" or "calico"`,
|
|
30
|
+
entity: resourceName,
|
|
31
|
+
lexicon: "azure",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return diagnostics;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR027: Container Registry admin user enabled
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Container Registry has the admin user enabled.
|
|
5
|
+
* Admin user provides shared credentials which cannot be individually
|
|
6
|
+
* revoked. Use Azure AD authentication or service principals instead.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const ACR_TYPE = "Microsoft.ContainerRegistry/registries";
|
|
10
|
+
export const azr027 = {
|
|
11
|
+
id: "AZR027",
|
|
12
|
+
description: "Container Registry admin user enabled — disable admin and use Azure AD or service principals",
|
|
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 !== ACR_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
if (props.adminUserEnabled === true) {
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
checkId: "AZR027",
|
|
27
|
+
severity: "warning",
|
|
28
|
+
message: `Container Registry "${resourceName}" has admin user enabled — disable adminUserEnabled and use Azure AD authentication`,
|
|
29
|
+
entity: resourceName,
|
|
30
|
+
lexicon: "azure",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return diagnostics;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR028: Network interface missing NSG
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Network Interface does not have a Network Security Group
|
|
5
|
+
* associated. NSGs provide essential firewall rules to control inbound
|
|
6
|
+
* and outbound traffic.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const NIC_TYPE = "Microsoft.Network/networkInterfaces";
|
|
10
|
+
export const azr028 = {
|
|
11
|
+
id: "AZR028",
|
|
12
|
+
description: "Network interface missing NSG — associate an NSG to control network traffic",
|
|
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 !== NIC_TYPE)
|
|
21
|
+
continue;
|
|
22
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
23
|
+
const props = resource.properties ?? {};
|
|
24
|
+
if (!props.networkSecurityGroup) {
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
checkId: "AZR028",
|
|
27
|
+
severity: "warning",
|
|
28
|
+
message: `Network interface "${resourceName}" does not have an NSG associated — set networkSecurityGroup.id`,
|
|
29
|
+
entity: resourceName,
|
|
30
|
+
lexicon: "azure",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return diagnostics;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR029: Managed disk missing encryption
|
|
3
|
+
*
|
|
4
|
+
* Warns when a managed disk resource does not have encryption
|
|
5
|
+
* configured. Encryption at rest protects data on managed disks
|
|
6
|
+
* against unauthorized access.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
const DISK_TYPE = "Microsoft.Compute/disks";
|
|
10
|
+
export const azr029 = {
|
|
11
|
+
id: "AZR029",
|
|
12
|
+
description: "Managed disk missing encryption — enable encryption 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 !== DISK_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: "AZR029",
|
|
28
|
+
severity: "warning",
|
|
29
|
+
message: `Managed disk "${resourceName}" does not have encryption configured — set encryption.type to "EncryptionAtRestWithPlatformKey" or use customer-managed keys`,
|
|
30
|
+
entity: resourceName,
|
|
31
|
+
lexicon: "azure",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return diagnostics;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR030: Resource deployed at an unsupported template scope
|
|
3
|
+
*
|
|
4
|
+
* Errors when a template contains a resource whose ARM schema does not
|
|
5
|
+
* define it at the template's deployment scope (#1545). The serializer
|
|
6
|
+
* picks the template $schema from the common scope of all resources; a
|
|
7
|
+
* template mixing, say, management groups (tenant-only) with virtual
|
|
8
|
+
* networks (resource-group-only) has no valid single-template scope —
|
|
9
|
+
* split the project so each scope deploys its own template (linked
|
|
10
|
+
* deployments cross scopes).
|
|
11
|
+
*/
|
|
12
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
13
|
+
import { deployScopesFor, scopeForTemplateSchema } from "../../deploy-scopes.js";
|
|
14
|
+
export const azr030 = {
|
|
15
|
+
id: "AZR030",
|
|
16
|
+
description: "Resource deployed at an unsupported template scope — split scopes into separate templates",
|
|
17
|
+
check(ctx) {
|
|
18
|
+
const diagnostics = [];
|
|
19
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
20
|
+
const template = parseArmTemplate(output);
|
|
21
|
+
if (!template?.resources)
|
|
22
|
+
continue;
|
|
23
|
+
const templateScope = scopeForTemplateSchema(template.$schema);
|
|
24
|
+
for (const resource of template.resources) {
|
|
25
|
+
if (typeof resource.type !== "string")
|
|
26
|
+
continue;
|
|
27
|
+
const scopes = deployScopesFor(resource.type);
|
|
28
|
+
if (scopes.includes(templateScope))
|
|
29
|
+
continue;
|
|
30
|
+
const resourceName = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
31
|
+
diagnostics.push({
|
|
32
|
+
checkId: "AZR030",
|
|
33
|
+
severity: "error",
|
|
34
|
+
message: `Resource "${resourceName}" (${resource.type}) deploys at ` +
|
|
35
|
+
`${scopes.join("/")} scope, but this template targets ${templateScope} scope — ` +
|
|
36
|
+
`move it to a project deployed at a scope its schema supports`,
|
|
37
|
+
entity: resourceName,
|
|
38
|
+
lexicon: "azure",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return diagnostics;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR031: Policy Assignment Not Enforced
|
|
3
|
+
*
|
|
4
|
+
* Flags Microsoft.Authorization/policyAssignments with enforcementMode
|
|
5
|
+
* DoNotEnforce. The assignment stays visible in the tree while denying
|
|
6
|
+
* nothing — the Azure shape of a disabled guardrail.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
export const azr031 = {
|
|
10
|
+
id: "AZR031",
|
|
11
|
+
description: "Policy assignment has enforcementMode DoNotEnforce — the guardrail is disabled",
|
|
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 !== "Microsoft.Authorization/policyAssignments")
|
|
20
|
+
continue;
|
|
21
|
+
const mode = resource.properties?.enforcementMode;
|
|
22
|
+
if (mode !== "DoNotEnforce")
|
|
23
|
+
continue;
|
|
24
|
+
const name = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
checkId: "AZR031",
|
|
27
|
+
severity: "error",
|
|
28
|
+
message: `Policy assignment "${name}" has enforcementMode DoNotEnforce — the guardrail is assigned but enforces nothing`,
|
|
29
|
+
entity: name,
|
|
30
|
+
lexicon: "azure",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return diagnostics;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR032: Policy Definition Assigned Nowhere
|
|
3
|
+
*
|
|
4
|
+
* Flags custom Microsoft.Authorization/policyDefinitions with no
|
|
5
|
+
* policyAssignments referencing them in the same template. The
|
|
6
|
+
* GovernanceBaseline composite always pairs definition and assignment; a
|
|
7
|
+
* definition alone is the leftover of a dropped guardrail assignment.
|
|
8
|
+
*/
|
|
9
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
10
|
+
/**
|
|
11
|
+
* True when a policyDefinitionId expression targets the definition name —
|
|
12
|
+
* either a quoted literal inside resourceId()/managementGroupResourceId()
|
|
13
|
+
* or the trailing segment of a literal resource id.
|
|
14
|
+
*/
|
|
15
|
+
function references(definitionName, policyDefinitionId) {
|
|
16
|
+
if (typeof policyDefinitionId !== "string")
|
|
17
|
+
return false;
|
|
18
|
+
return (policyDefinitionId.includes(`'${definitionName}'`) ||
|
|
19
|
+
policyDefinitionId.endsWith(`/${definitionName}`) ||
|
|
20
|
+
policyDefinitionId === definitionName);
|
|
21
|
+
}
|
|
22
|
+
export const azr032 = {
|
|
23
|
+
id: "AZR032",
|
|
24
|
+
description: "Custom policy definition assigned nowhere — a defined but unassigned guardrail enforces nothing",
|
|
25
|
+
check(ctx) {
|
|
26
|
+
const diagnostics = [];
|
|
27
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
28
|
+
const template = parseArmTemplate(output);
|
|
29
|
+
if (!template?.resources)
|
|
30
|
+
continue;
|
|
31
|
+
const assignmentIds = template.resources
|
|
32
|
+
.filter((r) => r.type === "Microsoft.Authorization/policyAssignments")
|
|
33
|
+
.map((r) => r.properties?.policyDefinitionId);
|
|
34
|
+
for (const resource of template.resources) {
|
|
35
|
+
if (resource.type !== "Microsoft.Authorization/policyDefinitions")
|
|
36
|
+
continue;
|
|
37
|
+
if (typeof resource.name !== "string")
|
|
38
|
+
continue;
|
|
39
|
+
const name = resource.name;
|
|
40
|
+
if (assignmentIds.some((id) => references(name, id)))
|
|
41
|
+
continue;
|
|
42
|
+
diagnostics.push({
|
|
43
|
+
checkId: "AZR032",
|
|
44
|
+
severity: "error",
|
|
45
|
+
message: `Policy definition "${name}" is assigned nowhere in this template — pair it with a policy assignment or the guardrail enforces nothing`,
|
|
46
|
+
entity: name,
|
|
47
|
+
lexicon: "azure",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return diagnostics;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AZR033: Subscriptions May Leave the Tenant
|
|
3
|
+
*
|
|
4
|
+
* Flags a Microsoft.Subscription/policies resource that sets
|
|
5
|
+
* blockSubscriptionsLeavingTenant to false — the tenant-level leave-block
|
|
6
|
+
* the landing-zone foundation declares, explicitly weakened.
|
|
7
|
+
*/
|
|
8
|
+
import { parseArmTemplate } from "./arm-refs.js";
|
|
9
|
+
export const azr033 = {
|
|
10
|
+
id: "AZR033",
|
|
11
|
+
description: "Tenant subscription policy does not block subscriptions from leaving",
|
|
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 !== "Microsoft.Subscription/policies")
|
|
20
|
+
continue;
|
|
21
|
+
if (resource.properties?.blockSubscriptionsLeavingTenant !== false)
|
|
22
|
+
continue;
|
|
23
|
+
const name = typeof resource.name === "string" ? resource.name : String(resource.name);
|
|
24
|
+
diagnostics.push({
|
|
25
|
+
checkId: "AZR033",
|
|
26
|
+
severity: "error",
|
|
27
|
+
message: `Subscription tenant policy "${name}" sets blockSubscriptionsLeavingTenant: false — subscriptions can be moved out of the tenant`,
|
|
28
|
+
entity: name,
|
|
29
|
+
lexicon: "azure",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return diagnostics;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { azr010 } from "./azr010.js";
|
|
2
|
+
import { azr011 } from "./azr011.js";
|
|
3
|
+
import { azr012 } from "./azr012.js";
|
|
4
|
+
import { azr013 } from "./azr013.js";
|
|
5
|
+
import { azr014 } from "./azr014.js";
|
|
6
|
+
import { azr015 } from "./azr015.js";
|
|
7
|
+
import { azr016 } from "./azr016.js";
|
|
8
|
+
import { azr017 } from "./azr017.js";
|
|
9
|
+
import { azr018 } from "./azr018.js";
|
|
10
|
+
import { azr019 } from "./azr019.js";
|
|
11
|
+
import { azr020 } from "./azr020.js";
|
|
12
|
+
import { azr021 } from "./azr021.js";
|
|
13
|
+
import { azr022 } from "./azr022.js";
|
|
14
|
+
import { azr023 } from "./azr023.js";
|
|
15
|
+
import { azr024 } from "./azr024.js";
|
|
16
|
+
import { azr025 } from "./azr025.js";
|
|
17
|
+
import { azr026 } from "./azr026.js";
|
|
18
|
+
import { azr027 } from "./azr027.js";
|
|
19
|
+
import { azr028 } from "./azr028.js";
|
|
20
|
+
import { azr029 } from "./azr029.js";
|
|
21
|
+
import { azr030 } from "./azr030.js";
|
|
22
|
+
import { azr031 } from "./azr031.js";
|
|
23
|
+
import { azr032 } from "./azr032.js";
|
|
24
|
+
import { azr033 } from "./azr033.js";
|
|
25
|
+
export const postSynthChecks = [
|
|
26
|
+
azr010,
|
|
27
|
+
azr011,
|
|
28
|
+
azr012,
|
|
29
|
+
azr013,
|
|
30
|
+
azr014,
|
|
31
|
+
azr015,
|
|
32
|
+
azr016,
|
|
33
|
+
azr017,
|
|
34
|
+
azr018,
|
|
35
|
+
azr019,
|
|
36
|
+
azr020,
|
|
37
|
+
azr021,
|
|
38
|
+
azr022,
|
|
39
|
+
azr023,
|
|
40
|
+
azr024,
|
|
41
|
+
azr025,
|
|
42
|
+
azr026,
|
|
43
|
+
azr027,
|
|
44
|
+
azr028,
|
|
45
|
+
azr029,
|
|
46
|
+
azr030,
|
|
47
|
+
azr031,
|
|
48
|
+
azr032,
|
|
49
|
+
azr033,
|
|
50
|
+
];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ts } from "@intentius/chant/lint/typescript";
|
|
2
|
+
/**
|
|
3
|
+
* AZR001: Hardcoded Azure Location
|
|
4
|
+
*
|
|
5
|
+
* Detects hardcoded Azure location strings in code.
|
|
6
|
+
* Locations should use Azure.ResourceGroupLocation pseudo-parameter instead.
|
|
7
|
+
*/
|
|
8
|
+
export const hardcodedLocationRule = {
|
|
9
|
+
id: "AZR001",
|
|
10
|
+
severity: "warning",
|
|
11
|
+
category: "correctness",
|
|
12
|
+
description: "Detects hardcoded Azure location strings — use Azure.ResourceGroupLocation instead",
|
|
13
|
+
check(context) {
|
|
14
|
+
const { sourceFile } = context;
|
|
15
|
+
const diagnostics = [];
|
|
16
|
+
// Common Azure location strings (case-insensitive match)
|
|
17
|
+
const azureLocations = new Set([
|
|
18
|
+
"eastus",
|
|
19
|
+
"eastus2",
|
|
20
|
+
"westus",
|
|
21
|
+
"westus2",
|
|
22
|
+
"westus3",
|
|
23
|
+
"centralus",
|
|
24
|
+
"northcentralus",
|
|
25
|
+
"southcentralus",
|
|
26
|
+
"westcentralus",
|
|
27
|
+
"canadacentral",
|
|
28
|
+
"canadaeast",
|
|
29
|
+
"brazilsouth",
|
|
30
|
+
"northeurope",
|
|
31
|
+
"westeurope",
|
|
32
|
+
"uksouth",
|
|
33
|
+
"ukwest",
|
|
34
|
+
"francecentral",
|
|
35
|
+
"francesouth",
|
|
36
|
+
"switzerlandnorth",
|
|
37
|
+
"switzerlandwest",
|
|
38
|
+
"germanywestcentral",
|
|
39
|
+
"norwayeast",
|
|
40
|
+
"norwaywest",
|
|
41
|
+
"swedencentral",
|
|
42
|
+
"eastasia",
|
|
43
|
+
"southeastasia",
|
|
44
|
+
"japaneast",
|
|
45
|
+
"japanwest",
|
|
46
|
+
"australiaeast",
|
|
47
|
+
"australiasoutheast",
|
|
48
|
+
"australiacentral",
|
|
49
|
+
"koreacentral",
|
|
50
|
+
"koreasouth",
|
|
51
|
+
"southafricanorth",
|
|
52
|
+
"southafricawest",
|
|
53
|
+
"uaenorth",
|
|
54
|
+
"uaecentral",
|
|
55
|
+
"centralindia",
|
|
56
|
+
"southindia",
|
|
57
|
+
"westindia",
|
|
58
|
+
"qatarcentral",
|
|
59
|
+
"polandcentral",
|
|
60
|
+
"italynorth",
|
|
61
|
+
]);
|
|
62
|
+
function visit(node) {
|
|
63
|
+
if (ts.isStringLiteral(node)) {
|
|
64
|
+
const value = node.text.toLowerCase();
|
|
65
|
+
if (azureLocations.has(value)) {
|
|
66
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
67
|
+
diagnostics.push({
|
|
68
|
+
file: sourceFile.fileName,
|
|
69
|
+
line: line + 1,
|
|
70
|
+
column: character + 1,
|
|
71
|
+
ruleId: "AZR001",
|
|
72
|
+
severity: "warning",
|
|
73
|
+
message: `Hardcoded location "${node.text}" detected. Use Azure.ResourceGroupLocation instead.`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
ts.forEachChild(node, visit);
|
|
78
|
+
}
|
|
79
|
+
visit(sourceFile);
|
|
80
|
+
return diagnostics;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ts } from "@intentius/chant/lint/typescript";
|
|
2
|
+
/**
|
|
3
|
+
* AZR003: NSG Wildcard Source Address
|
|
4
|
+
*
|
|
5
|
+
* Detects Network Security Group rules with sourceAddressPrefix: "*".
|
|
6
|
+
* Wildcard source addresses allow traffic from any source and should be
|
|
7
|
+
* restricted to specific IP ranges or service tags.
|
|
8
|
+
*/
|
|
9
|
+
export const nsgWildcardRule = {
|
|
10
|
+
id: "AZR003",
|
|
11
|
+
severity: "warning",
|
|
12
|
+
category: "security",
|
|
13
|
+
description: "Detects NSG rules with wildcard (*) source address — restrict to specific IP ranges or service tags",
|
|
14
|
+
check(context) {
|
|
15
|
+
const { sourceFile } = context;
|
|
16
|
+
const diagnostics = [];
|
|
17
|
+
function visit(node) {
|
|
18
|
+
// Look for sourceAddressPrefix: "*" in object literals
|
|
19
|
+
if (ts.isPropertyAssignment(node)) {
|
|
20
|
+
const name = node.name;
|
|
21
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
22
|
+
const propName = ts.isIdentifier(name) ? name.text : name.text;
|
|
23
|
+
if (propName === "sourceAddressPrefix") {
|
|
24
|
+
// Check if value is "*"
|
|
25
|
+
if (ts.isStringLiteral(node.initializer) && node.initializer.text === "*") {
|
|
26
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
file: sourceFile.fileName,
|
|
29
|
+
line: line + 1,
|
|
30
|
+
column: character + 1,
|
|
31
|
+
ruleId: "AZR003",
|
|
32
|
+
severity: "warning",
|
|
33
|
+
message: "NSG rule uses wildcard (*) sourceAddressPrefix. Restrict to specific IP ranges or service tags.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
ts.forEachChild(node, visit);
|
|
40
|
+
}
|
|
41
|
+
visit(sourceFile);
|
|
42
|
+
return diagnostics;
|
|
43
|
+
},
|
|
44
|
+
};
|