Родитель
2d481222e9
Коммит
db1a19f0c9
|
@ -32,6 +32,7 @@
|
|||
"SKUs",
|
||||
"VNET",
|
||||
"VNETs",
|
||||
"agentpool",
|
||||
"cmdlet",
|
||||
"cmdlets",
|
||||
"endregion",
|
||||
|
|
|
@ -38,7 +38,9 @@
|
|||
"label": "coverage",
|
||||
"type": "shell",
|
||||
"command": "Invoke-Build Test -CodeCoverage",
|
||||
"problemMatcher": [ "$pester" ],
|
||||
"problemMatcher": [
|
||||
"$pester"
|
||||
],
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "dedicated"
|
||||
|
|
|
@ -7,6 +7,13 @@ See [troubleshooting guide] for a workaround to this issue.
|
|||
|
||||
## Unreleased
|
||||
|
||||
- General improvements:
|
||||
- Automatically nest template sub-resources for analysis. [#746](https://github.com/Microsoft/PSRule.Rules.Azure/issues/746)
|
||||
- Sub-resources such as diagnostic logs or configurations are automatically nested.
|
||||
- Automatic nesting a resource requires:
|
||||
- The parent resource is defined in the same template.
|
||||
- The sub-resource depends on the parent resource.
|
||||
|
||||
## v1.3.2
|
||||
|
||||
What's changed since v1.3.1:
|
||||
|
|
|
@ -52,6 +52,10 @@ Currently the following limitations apply:
|
|||
|
||||
- Nested templates are expanded, external templates are not.
|
||||
- Deployment resources that link to an external template are returned as a resource.
|
||||
- Sub-resources such as diagnostic logs or configurations are automatically nested.
|
||||
Automatic nesting a sub-resource requires:
|
||||
- The parent resource is defined in the same template.
|
||||
- The sub-resource depends on the parent resource.
|
||||
- The `pickZones` template function is not supported.
|
||||
- The `environment` template function always returns values for Azure public cloud.
|
||||
- References to Key Vault secrets are not expanded.
|
||||
|
|
|
@ -172,7 +172,11 @@ task PSRule NuGet, {
|
|||
if ($Null -eq (Get-InstalledModule -Name PSRule -MinimumVersion 1.3.0 -ErrorAction Ignore)) {
|
||||
Install-Module -Name PSRule -Repository PSGallery -MinimumVersion 1.3.0 -Scope CurrentUser -Force;
|
||||
}
|
||||
if ($Null -eq (Get-InstalledModule -Name PSRule.Rules.MSFT.OSS -MinimumVersion 0.1.0 -AllowPrerelease -ErrorAction Ignore)) {
|
||||
Install-Module -Name PSRule.Rules.MSFT.OSS -Repository PSGallery -MinimumVersion 0.1.0 -AllowPrerelease -Scope CurrentUser -Force;
|
||||
}
|
||||
Import-Module -Name PSRule -Verbose:$False;
|
||||
Import-Module -Name PSRule.Rules.MSFT.OSS -Verbose:$False;
|
||||
}
|
||||
|
||||
# Synopsis: Install PSDocs
|
||||
|
@ -314,7 +318,7 @@ task Rules PSRule, {
|
|||
ErrorAction = 'Stop'
|
||||
}
|
||||
Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.Azure) -Force;
|
||||
Assert-PSRule @assertParams -InputPath $PWD -Format File -OutputPath reports/ps-rule-file.xml;
|
||||
Assert-PSRule @assertParams -InputPath $PWD -Module PSRule.Rules.MSFT.OSS -Format File -OutputPath reports/ps-rule-file.xml;
|
||||
|
||||
$rules = Get-PSRule -Module PSRule.Rules.Azure;
|
||||
$rules | Assert-PSRule @assertParams -OutputPath reports/ps-rule-file2.xml;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace PSRule.Rules.Azure
|
||||
{
|
||||
|
@ -38,6 +39,8 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
internal static class JsonExtensions
|
||||
{
|
||||
private const string FIELD_DEPENDSON = "dependsOn";
|
||||
|
||||
internal static IJsonLineInfo TryLineInfo(this JToken token)
|
||||
{
|
||||
if (token == null)
|
||||
|
@ -75,5 +78,26 @@ namespace PSRule.Rules.Azure
|
|||
if (annotation != null)
|
||||
token.AddAnnotation(annotation);
|
||||
}
|
||||
|
||||
internal static void UseProperty<TValue>(this JObject o, string propertyName, out TValue value) where TValue : JToken, new()
|
||||
{
|
||||
if (!o.TryGetValue(propertyName, System.StringComparison.OrdinalIgnoreCase, out JToken v))
|
||||
{
|
||||
value = new TValue();
|
||||
o.Add(propertyName, value);
|
||||
return;
|
||||
}
|
||||
value = (TValue)v;
|
||||
}
|
||||
|
||||
internal static bool TryGetDependencies(this JObject resource, out string[] dependencies)
|
||||
{
|
||||
dependencies = null;
|
||||
if (!(resource.ContainsKey(FIELD_DEPENDSON) && resource[FIELD_DEPENDSON] is JArray d && d.Count > 0))
|
||||
return false;
|
||||
|
||||
dependencies = d.Values<string>().ToArray();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,8 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
private readonly Stack<JObject> _Deployment;
|
||||
private readonly ExpressionFactory _ExpressionFactory;
|
||||
private readonly ExpressionBuilder _ExpressionBuilder;
|
||||
private readonly List<ResourceValue> _Resources;
|
||||
private readonly Dictionary<string, ResourceValue> _ResourceIds;
|
||||
|
||||
private JObject _Parameters;
|
||||
private Dictionary<string, ResourceProvider> _Providers;
|
||||
|
@ -98,7 +100,8 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
|
||||
internal TemplateContext()
|
||||
{
|
||||
Resources = new List<JObject>();
|
||||
_Resources = new List<ResourceValue>();
|
||||
_ResourceIds = new Dictionary<string, ResourceValue>(StringComparer.OrdinalIgnoreCase);
|
||||
Parameters = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
Variables = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
CopyIndex = new CopyIndexStore();
|
||||
|
@ -120,8 +123,6 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
ResourceGroup = resourceGroup;
|
||||
}
|
||||
|
||||
public List<JObject> Resources { get; }
|
||||
|
||||
private Dictionary<string, object> Parameters { get; }
|
||||
|
||||
private Dictionary<string, object> Variables { get; }
|
||||
|
@ -142,6 +143,48 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
return _ExpressionBuilder.Build(s);
|
||||
}
|
||||
|
||||
public void AddResource(JObject resource)
|
||||
{
|
||||
if (resource == null)
|
||||
return;
|
||||
|
||||
GetResourceNameType(resource, out string[] nameParts, out string[] typeParts);
|
||||
var resourceId = GetResourceId(nameParts, typeParts);
|
||||
AddResource(new ResourceValue(resourceId, resource));
|
||||
}
|
||||
|
||||
private void AddResource(ResourceValue resource)
|
||||
{
|
||||
_Resources.Add(resource);
|
||||
_ResourceIds[resource.ResourceId] = resource;
|
||||
}
|
||||
|
||||
public void AddResource(ResourceValue[] resource)
|
||||
{
|
||||
for (var i = 0; resource != null && i < resource.Length; i++)
|
||||
AddResource(resource[i]);
|
||||
}
|
||||
|
||||
public ResourceValue[] GetResources()
|
||||
{
|
||||
return _Resources.ToArray();
|
||||
}
|
||||
|
||||
public void RemoveResource(ResourceValue resource)
|
||||
{
|
||||
_Resources.Remove(resource);
|
||||
_ResourceIds.Remove(resource.ResourceId);
|
||||
}
|
||||
|
||||
public bool TryGetResource(string resourceId, out ResourceValue resource)
|
||||
{
|
||||
if (_ResourceIds.TryGetValue(resourceId, out resource))
|
||||
return true;
|
||||
|
||||
resource = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void WriteDebug(string message, params object[] args)
|
||||
{
|
||||
if (Pipeline == null || Pipeline.Writer == null || string.IsNullOrEmpty(message) || !Pipeline.Writer.ShouldWriteDebug())
|
||||
|
@ -163,6 +206,67 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
}
|
||||
}
|
||||
|
||||
private static string GetResourceId(string[] nameParts, string[] typeParts, int depth = -1)
|
||||
{
|
||||
if (depth == -1)
|
||||
depth = nameParts.Length;
|
||||
|
||||
var name = new string[2 * depth + 1];
|
||||
var nameIndex = 0;
|
||||
var typeIndex = 0;
|
||||
name[0] = typeParts[typeIndex++];
|
||||
name[1] = typeParts[typeIndex++];
|
||||
name[2] = nameParts[nameIndex++];
|
||||
for (var i = 3; i < 2 * depth + 1; i = i + 2)
|
||||
{
|
||||
name[i] = typeParts[typeIndex++];
|
||||
name[i + 1] = nameParts[nameIndex++];
|
||||
}
|
||||
return string.Join("/", name);
|
||||
}
|
||||
|
||||
internal static bool TryParentResourceId(JObject resource, out string[] resourceId)
|
||||
{
|
||||
resourceId = null;
|
||||
if (GetResourceScope(resource, out resourceId))
|
||||
return true;
|
||||
|
||||
if (!GetResourceNameType(resource, out string[] nameParts, out string[] typeParts))
|
||||
return false;
|
||||
|
||||
resourceId = new string[nameParts.Length - 1];
|
||||
for (var i = 0; i < nameParts.Length - 1; i++)
|
||||
resourceId[i] = GetResourceId(nameParts, typeParts, i + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool GetResourceNameType(JObject resource, out string[] nameParts, out string[] typeParts)
|
||||
{
|
||||
var name = resource[PROPERTY_NAME].Value<string>();
|
||||
nameParts = name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
typeParts = null;
|
||||
if (nameParts == null || nameParts.Length == 0)
|
||||
return false;
|
||||
|
||||
var type = resource[PROPERTY_TYPE].Value<string>();
|
||||
typeParts = type.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (typeParts == null || typeParts.Length != nameParts.Length + 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool GetResourceScope(JObject resource, out string[] resourceId)
|
||||
{
|
||||
resourceId = null;
|
||||
if (!resource.ContainsKey(PROPERTY_SCOPE))
|
||||
return false;
|
||||
|
||||
resourceId = new string[] { resource[PROPERTY_SCOPE].Value<string>() };
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryParameterValue(string parameterName, JObject parameter)
|
||||
{
|
||||
if (!parameter.ContainsKey(PROPERTY_VALUE))
|
||||
|
@ -465,6 +569,19 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{ResourceId}")]
|
||||
internal sealed class ResourceValue
|
||||
{
|
||||
internal readonly string ResourceId;
|
||||
internal readonly JObject Value;
|
||||
|
||||
internal ResourceValue(string resourceId, JObject value)
|
||||
{
|
||||
ResourceId = resourceId;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class LazyValue
|
||||
{
|
||||
public abstract object GetValue(TemplateContext context);
|
||||
|
@ -517,7 +634,14 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
|
||||
public void Visit(TemplateContext context, string deploymentName, JObject template)
|
||||
{
|
||||
BeginTemplate(context, deploymentName, template);
|
||||
Template(context, deploymentName, template);
|
||||
EndTemplate(context, deploymentName, template);
|
||||
}
|
||||
|
||||
protected virtual void BeginTemplate(TemplateContext context, string deploymentName, JObject template)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Template(TemplateContext context, string deploymentName, JObject template)
|
||||
|
@ -540,7 +664,7 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
Variables(context, variables);
|
||||
|
||||
if (TryArrayProperty(template, PROPERTY_RESOURCES, out JArray resources))
|
||||
Resources(context, resources.Values<JObject>());
|
||||
Resources(context, resources.Values<JObject>().ToArray());
|
||||
|
||||
if (TryObjectProperty(template, PROPERTY_OUTPUTS, out JObject outputs))
|
||||
Outputs(context, outputs);
|
||||
|
@ -551,6 +675,11 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
}
|
||||
}
|
||||
|
||||
protected virtual void EndTemplate(TemplateContext context, string deploymentName, JObject template)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Schema(TemplateContext context, string schema)
|
||||
{
|
||||
|
||||
|
@ -641,13 +770,14 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
|
||||
#region Resources
|
||||
|
||||
protected virtual void Resources(TemplateContext context, IEnumerable<JObject> resources)
|
||||
protected virtual void Resources(TemplateContext context, JObject[] resources)
|
||||
{
|
||||
if (resources == null)
|
||||
if (resources == null || resources.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var resource in resources)
|
||||
ResourceOuter(context, resource);
|
||||
resources = SortResources(context, resources);
|
||||
for (var i = 0; i < resources.Length; i++)
|
||||
ResourceOuter(context, resources[i]);
|
||||
}
|
||||
|
||||
private void ResourceOuter(TemplateContext context, JObject resource)
|
||||
|
@ -721,8 +851,8 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
return false;
|
||||
|
||||
Template(deploymentContext, deploymentName, template);
|
||||
if (deploymentContext != context && deploymentContext.Resources != null && deploymentContext.Resources.Count > 0)
|
||||
context.Resources.AddRange(deploymentContext.Resources);
|
||||
if (deploymentContext != context)
|
||||
context.AddResource(deploymentContext.GetResources());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1124,7 +1254,21 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
/// </summary>
|
||||
protected virtual void Emit(TemplateContext context, JObject resource)
|
||||
{
|
||||
context.Resources.Add(resource);
|
||||
context.AddResource(resource);
|
||||
}
|
||||
|
||||
protected virtual JObject[] SortResources(TemplateContext context, JObject[] resources)
|
||||
{
|
||||
//var source = new List<JObject>(resources);
|
||||
//var result = new List<JObject>(resources.Length);
|
||||
|
||||
|
||||
//for (var i = 0; i < resources.Length; i++)
|
||||
//{
|
||||
|
||||
//}
|
||||
//return result.ToArray();
|
||||
return resources;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1133,22 +1277,62 @@ namespace PSRule.Rules.Azure.Data.Template
|
|||
/// </summary>
|
||||
internal sealed class RuleDataExportVisitor : TemplateVisitor
|
||||
{
|
||||
private const string FIELD_DEPENDSON = "dependsOn";
|
||||
private const string FIELD_COMMENTS = "comments";
|
||||
private const string FIELD_APIVERSION = "apiVersion";
|
||||
private const string FIELD_CONDITION = "condition";
|
||||
private const string FIELD_RESOURCES = "resources";
|
||||
|
||||
private readonly List<JObject> _Resources;
|
||||
private readonly Dictionary<string, JObject> _ResourceMap;
|
||||
|
||||
internal RuleDataExportVisitor()
|
||||
{
|
||||
_Resources = new List<JObject>();
|
||||
_ResourceMap = new Dictionary<string, JObject>();
|
||||
}
|
||||
|
||||
protected override void Resource(TemplateContext context, JObject resource)
|
||||
{
|
||||
// Remove resource properties that not required in rule data
|
||||
if (resource.ContainsKey("apiVersion"))
|
||||
resource.Remove("apiVersion");
|
||||
if (resource.ContainsKey(FIELD_APIVERSION))
|
||||
resource.Remove(FIELD_APIVERSION);
|
||||
|
||||
if (resource.ContainsKey("condition"))
|
||||
resource.Remove("condition");
|
||||
if (resource.ContainsKey(FIELD_CONDITION))
|
||||
resource.Remove(FIELD_CONDITION);
|
||||
|
||||
if (resource.ContainsKey("comments"))
|
||||
resource.Remove("comments");
|
||||
if (resource.ContainsKey(FIELD_COMMENTS))
|
||||
resource.Remove(FIELD_COMMENTS);
|
||||
|
||||
if (resource.ContainsKey("dependsOn"))
|
||||
resource.Remove("dependsOn");
|
||||
if (!resource.TryGetDependencies(out _))
|
||||
resource.Remove(FIELD_DEPENDSON);
|
||||
|
||||
base.Resource(context, resource);
|
||||
}
|
||||
|
||||
protected override void EndTemplate(TemplateContext context, string deploymentName, JObject template)
|
||||
{
|
||||
var resources = context.GetResources();
|
||||
for (var i = 0; i < resources.Length; i++)
|
||||
{
|
||||
if (resources[i].Value.TryGetDependencies(out string[] dependencies))
|
||||
{
|
||||
resources[i].Value.Remove(FIELD_DEPENDSON);
|
||||
if (TemplateContext.TryParentResourceId(resources[i].Value, out string[] parentResourceId))
|
||||
{
|
||||
for (var j = 0; j < parentResourceId.Length; j++)
|
||||
{
|
||||
if (context.TryGetResource(parentResourceId[j], out ResourceValue resource))
|
||||
{
|
||||
resource.Value.UseProperty(FIELD_RESOURCES, out JArray innerResources);
|
||||
innerResources.Add(resources[i].Value);
|
||||
context.RemoveResource(resources[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
base.EndTemplate(context, deploymentName, template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -218,8 +218,8 @@ namespace PSRule.Rules.Azure.Pipeline
|
|||
var results = new List<PSObject>();
|
||||
var serializer = new JsonSerializer();
|
||||
serializer.Converters.Add(new PSObjectJsonConverter());
|
||||
foreach (var resource in context.Resources)
|
||||
results.Add(resource.ToObject<PSObject>(serializer));
|
||||
foreach (var resource in context.GetResources())
|
||||
results.Add(resource.Value.ToObject<PSObject>(serializer));
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
|
|
@ -14,8 +14,11 @@ Rule 'Azure.AKS.MinNodeCount' -Type 'Microsoft.ContainerService/managedClusters'
|
|||
Rule 'Azure.AKS.Version' -Type 'Microsoft.ContainerService/managedClusters', 'Microsoft.ContainerService/managedClusters/agentPools' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
|
||||
$minVersion = [Version]$Configuration.Azure_AKSMinimumVersion
|
||||
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
|
||||
([Version]$TargetObject.Properties.kubernetesVersion) -ge $minVersion
|
||||
Reason ($LocalizedData.AKSVersion -f $TargetObject.Properties.kubernetesVersion);
|
||||
$Assert.Create(
|
||||
(([Version]$TargetObject.Properties.kubernetesVersion) -ge $minVersion),
|
||||
$LocalizedData.AKSVersion,
|
||||
$TargetObject.Properties.kubernetesVersion
|
||||
);
|
||||
}
|
||||
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
|
||||
$Assert.NullOrEmpty($TargetObject, 'Properties.orchestratorVersion').Result -or
|
||||
|
@ -26,11 +29,14 @@ Rule 'Azure.AKS.Version' -Type 'Microsoft.ContainerService/managedClusters', 'Mi
|
|||
|
||||
# Synopsis: AKS agent pools should run the same Kubernetes version as the cluster
|
||||
Rule 'Azure.AKS.PoolVersion' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
|
||||
$clusterVersion = $TargetObject.Properties.kubernetesVersion
|
||||
foreach ($pool in $TargetObject.Properties.agentPoolProfiles) {
|
||||
$Assert.
|
||||
HasDefaultValue($pool, 'orchestratorVersion', $clusterVersion).
|
||||
Reason($LocalizedData.AKSNodePoolVersion, $pool.name, $pool.orchestratorVersion)
|
||||
$clusterVersion = $TargetObject.Properties.kubernetesVersion;
|
||||
$agentPools = @(GetAgentPoolProfiles);
|
||||
if ($agentPools.Length -eq 0) {
|
||||
return $Assert.Pass();
|
||||
}
|
||||
foreach ($agentPool in $agentPools) {
|
||||
$Assert.HasDefaultValue($agentPool, 'orchestratorVersion', $clusterVersion).
|
||||
Reason($LocalizedData.AKSNodePoolVersion, $agentPool.name, $agentPool.orchestratorVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,37 +52,24 @@ Rule 'Azure.AKS.NetworkPolicy' -Type 'Microsoft.ContainerService/managedClusters
|
|||
|
||||
# Synopsis: AKS node pools should use scale sets
|
||||
Rule 'Azure.AKS.PoolScaleSet' -Type 'Microsoft.ContainerService/managedClusters', 'Microsoft.ContainerService/managedClusters/agentPools' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
|
||||
$result = $True
|
||||
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
|
||||
$TargetObject.Properties.agentPoolProfiles | ForEach-Object {
|
||||
if ($_.type -ne 'VirtualMachineScaleSets') {
|
||||
$Assert.Fail($LocalizedData.AKSNodePoolType, $_.name)
|
||||
}
|
||||
}
|
||||
$agentPools = @(GetAgentPoolProfiles);
|
||||
if ($agentPools.Length -eq 0) {
|
||||
return $Assert.Pass();
|
||||
}
|
||||
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
|
||||
$result = $TargetObject.Properties.type -eq 'VirtualMachineScaleSets'
|
||||
if (!$result) {
|
||||
$Assert.Fail($LocalizedData.AKSNodePoolType, $TargetObject.name)
|
||||
}
|
||||
foreach ($agentPool in $agentPools) {
|
||||
$Assert.HasFieldValue($agentPool, 'type', 'VirtualMachineScaleSets').
|
||||
Reason($LocalizedData.AKSNodePoolType, $agentPool.name);
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
# Synopsis: AKS nodes should use a minimum number of pods
|
||||
Rule 'Azure.AKS.NodeMinPods' -Type 'Microsoft.ContainerService/managedClusters', 'Microsoft.ContainerService/managedClusters/agentPools' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
|
||||
$agentPools = $Null;
|
||||
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
|
||||
$agentPools = @($TargetObject.Properties.agentPoolProfiles);
|
||||
if ($agentPools.Length -eq 0) {
|
||||
return $Assert.Pass();
|
||||
}
|
||||
}
|
||||
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
|
||||
$agentPools = @($TargetObject.Properties);
|
||||
$agentPools = @(GetAgentPoolProfiles);
|
||||
if ($agentPools.Length -eq 0) {
|
||||
return $Assert.Pass();
|
||||
}
|
||||
foreach ($agentPool in $agentPools) {
|
||||
$Assert.GreaterOrEqual($agentPool, 'maxPods', $Configuration.Azure_AKSNodeMinimumMaxPods)
|
||||
$Assert.GreaterOrEqual($agentPool, 'maxPods', $Configuration.Azure_AKSNodeMinimumMaxPods);
|
||||
}
|
||||
} -Configure @{ Azure_AKSNodeMinimumMaxPods = 50 }
|
||||
|
||||
|
@ -85,23 +78,23 @@ Rule 'Azure.AKS.Name' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{
|
|||
# https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftcontainerservice
|
||||
|
||||
# Between 1 and 63 characters long
|
||||
$Assert.GreaterOrEqual($PSRule, 'TargetName', 1)
|
||||
$Assert.LessOrEqual($PSRule, 'TargetName', 63)
|
||||
$Assert.GreaterOrEqual($PSRule, 'TargetName', 1);
|
||||
$Assert.LessOrEqual($PSRule, 'TargetName', 63);
|
||||
|
||||
# Alphanumerics, underscores, and hyphens
|
||||
# Start and end with alphanumeric
|
||||
$Assert.Match($PSRule, 'TargetName', '^[A-Za-z0-9](-|\w)*[A-Za-z0-9]$')
|
||||
$Assert.Match($PSRule, 'TargetName', '^[A-Za-z0-9](-|\w)*[A-Za-z0-9]$');
|
||||
}
|
||||
|
||||
# Synopsis: Use AKS naming requirements for DNS prefix
|
||||
Rule 'Azure.AKS.DNSPrefix' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
|
||||
# Between 1 and 54 characters long
|
||||
$Assert.GreaterOrEqual($TargetObject, 'Properties.dnsPrefix', 1)
|
||||
$Assert.LessOrEqual($TargetObject, 'Properties.dnsPrefix', 54)
|
||||
$Assert.GreaterOrEqual($TargetObject, 'Properties.dnsPrefix', 1);
|
||||
$Assert.LessOrEqual($TargetObject, 'Properties.dnsPrefix', 54);
|
||||
|
||||
# Alphanumerics and hyphens
|
||||
# Start and end with alphanumeric
|
||||
$Assert.Match($TargetObject, 'Properties.dnsPrefix', '^[A-Za-z0-9]((-|[A-Za-z0-9]){0,}[A-Za-z0-9]){0,}$')
|
||||
$Assert.Match($TargetObject, 'Properties.dnsPrefix', '^[A-Za-z0-9]((-|[A-Za-z0-9]){0,}[A-Za-z0-9]){0,}$');
|
||||
}
|
||||
|
||||
# Synopsis: Configure AKS clusters to use managed identities for managing cluster infrastructure.
|
||||
|
@ -111,10 +104,41 @@ Rule 'Azure.AKS.ManagedIdentity' -Type 'Microsoft.ContainerService/managedCluste
|
|||
|
||||
# Synopsis: Use a Standard load-balancer with AKS clusters
|
||||
Rule 'Azure.AKS.StandardLB' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2020_06' } {
|
||||
$Assert.HasFieldValue($TargetObject, 'Properties.networkProfile.loadBalancerSku', 'standard')
|
||||
$Assert.HasFieldValue($TargetObject, 'Properties.networkProfile.loadBalancerSku', 'standard');
|
||||
}
|
||||
|
||||
# Synopsis: AKS clusters should use Azure Policy add-on
|
||||
Rule 'Azure.AKS.AzurePolicyAddOn' -Type 'Microsoft.ContainerService/managedClusters' -Tag @{ release = 'GA'; ruleSet = '2020_12' } {
|
||||
$Assert.HasFieldValue($TargetObject, 'Properties.addonProfiles.azurePolicy.enabled', $True)
|
||||
$Assert.HasFieldValue($TargetObject, 'Properties.addonProfiles.azurePolicy.enabled', $True);
|
||||
}
|
||||
|
||||
#region Helper functions
|
||||
|
||||
function global:GetAgentPoolProfiles {
|
||||
[CmdletBinding()]
|
||||
[OutputType([PSObject])]
|
||||
param ()
|
||||
process {
|
||||
if ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters') {
|
||||
$TargetObject.Properties.agentPoolProfiles;
|
||||
@(GetSubResources -ResourceType 'Microsoft.ContainerService/managedClusters/agentPools' | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
name = $_.name
|
||||
type = $_.type
|
||||
maxPods = $_.properties.maxPods
|
||||
orchestratorVersion = $_.properties.orchestratorVersion
|
||||
}
|
||||
});
|
||||
}
|
||||
elseif ($PSRule.TargetType -eq 'Microsoft.ContainerService/managedClusters/agentPools') {
|
||||
[PSCustomObject]@{
|
||||
name = $TargetObject.name
|
||||
type = $TargetObject.properties.type
|
||||
maxPods = $TargetObject.properties.maxPods
|
||||
orchestratorVersion = $TargetObject.properties.orchestratorVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Helper functions
|
||||
|
|
|
@ -103,7 +103,7 @@ function global:SupportsAcceleratedNetworking {
|
|||
}
|
||||
|
||||
$vmSize = $TargetObject.Properties.hardwareProfile.vmSize;
|
||||
if ($vmSize -notlike 'Standard_*_*') {
|
||||
if ($vmSize -notLike 'Standard_*_*') {
|
||||
if ($vmSize -match '^Standard_(F|B[1-2][0-9]ms)') {
|
||||
return $True;
|
||||
}
|
||||
|
@ -122,13 +122,13 @@ function global:SupportsAcceleratedNetworking {
|
|||
|
||||
# Generation v2
|
||||
if ($generation -eq 'v2') {
|
||||
if ($size -notmatch '^(A|NC|DS1$|D1$|F[1-2]s)') {
|
||||
if ($size -notMatch '^(A|NC|DS1$|D1$|F[1-2]s)') {
|
||||
return $True;
|
||||
}
|
||||
}
|
||||
# Generation v3
|
||||
elseif ($generation -eq 'v3') {
|
||||
if ($size -notmatch '^(E2s?|E[2-8]-2|D2s?|NC)') {
|
||||
if ($size -notMatch '^(E2s?|E[2-8]-2|D2s?|NC)') {
|
||||
return $True;
|
||||
}
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ function global:IsAppGwWAF {
|
|||
if ($PSRule.TargetType -ne 'Microsoft.Network/applicationGateways') {
|
||||
return $False;
|
||||
}
|
||||
if ($TargetObject.Properties.sku.tier -notin ('WAF', 'WAF_v2')) {
|
||||
if ($TargetObject.Properties.sku.tier -notIn ('WAF', 'WAF_v2')) {
|
||||
return $False;
|
||||
}
|
||||
if ($TargetObject.Properties.webApplicationFirewallConfiguration.enabled -ne $True) {
|
||||
|
@ -179,7 +179,7 @@ function global:IsWindowsOS {
|
|||
[OutputType([System.Boolean])]
|
||||
param ()
|
||||
process {
|
||||
if ($PSRule.TargetType -notin 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets') {
|
||||
if ($PSRule.TargetType -notIn 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets') {
|
||||
return $False;
|
||||
}
|
||||
return ($TargetObject.Properties.storageProfile.osDisk.osType -eq 'Windows') -or
|
||||
|
@ -194,7 +194,7 @@ function global:IsWindowsClientOS {
|
|||
[OutputType([System.Boolean])]
|
||||
param ()
|
||||
process {
|
||||
if ($PSRule.TargetType -notin 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets') {
|
||||
if ($PSRule.TargetType -notIn 'Microsoft.Compute/virtualMachines', 'Microsoft.Compute/virtualMachineScaleSets') {
|
||||
return $False;
|
||||
}
|
||||
return $TargetObject.Properties.storageProfile.imageReference.publisher -eq 'MicrosoftWindowsDesktop';
|
||||
|
@ -236,7 +236,7 @@ function global:SupportsTags {
|
|||
process {
|
||||
if (
|
||||
($PSRule.TargetType -eq 'Microsoft.Subscription') -or
|
||||
($PSRule.TargetType -notlike 'Microsoft.*/*') -or
|
||||
($PSRule.TargetType -notLike 'Microsoft.*/*') -or
|
||||
($PSRule.TargetType -like 'Microsoft.Addons/*') -or
|
||||
($PSRule.TargetType -like 'Microsoft.Advisor/*') -or
|
||||
($PSRule.TargetType -like 'Microsoft.Authorization/*') -or
|
||||
|
|
|
@ -63,8 +63,8 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 3;
|
||||
$ruleResult.TargetName | Should -BeIn 'cluster-A', 'cluster-C', 'cluster-D';
|
||||
$ruleResult.Length | Should -Be 4;
|
||||
$ruleResult.TargetName | Should -BeIn 'cluster-A', 'cluster-C', 'cluster-D', 'system';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.PoolVersion' {
|
||||
|
@ -127,8 +127,8 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 2;
|
||||
$ruleResult.TargetName | Should -Be 'cluster-C', 'cluster-D';
|
||||
$ruleResult.Length | Should -Be 3;
|
||||
$ruleResult.TargetName | Should -BeIn 'cluster-C', 'cluster-D', 'system';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.NodeMinPods' {
|
||||
|
@ -143,8 +143,8 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 2;
|
||||
$ruleResult.TargetName | Should -Be 'cluster-C', 'cluster-D';
|
||||
$ruleResult.Length | Should -Be 3;
|
||||
$ruleResult.TargetName | Should -BeIn 'cluster-C', 'cluster-D', 'system';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.ManagedIdentity' {
|
||||
|
@ -312,25 +312,29 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
|
||||
# Fail
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
$ruleResult.TargetName | Should -Be 'clusterA/agentpool2';
|
||||
$ruleResult | Should -BeNullOrEmpty;
|
||||
|
||||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 3;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterA/agentpool3', 'clusterA/agentpool4';
|
||||
$ruleResult.Length | Should -Be 4;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterB', 'clusterC/agentpool3', 'clusterC/agentpool4';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.PoolVersion' {
|
||||
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.AKS.PoolVersion' };
|
||||
|
||||
# Fail
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA';
|
||||
|
||||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA';
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterB';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.PoolScaleSet' {
|
||||
|
@ -340,13 +344,13 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
$ruleResult.TargetName | Should -Be 'clusterA/agentpool2';
|
||||
$ruleResult.TargetName | Should -Be 'clusterA';
|
||||
|
||||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 3;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterA/agentpool3', 'clusterA/agentpool4';
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterB', 'clusterC/agentpool3', 'clusterC/agentpool4';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.NodeMinPods' {
|
||||
|
@ -356,13 +360,13 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
$ruleResult.TargetName | Should -Be 'clusterA/agentpool2';
|
||||
$ruleResult.TargetName | Should -Be 'clusterA';
|
||||
|
||||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 3;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterA/agentpool3', 'clusterA/agentpool4';
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterB', 'clusterC/agentpool3', 'clusterC/agentpool4';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.ManagedIdentity' {
|
||||
|
@ -375,8 +379,8 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA';
|
||||
$ruleResult.Length | Should -Be 2;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterB';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.StandardLB' {
|
||||
|
@ -389,8 +393,8 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA';
|
||||
$ruleResult.Length | Should -Be 2;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterA', 'clusterB';
|
||||
}
|
||||
|
||||
It 'Azure.AKS.AzurePolicyAddOn' {
|
||||
|
@ -398,7 +402,9 @@ Describe 'Azure.AKS' -Tag AKS {
|
|||
|
||||
# Fail
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
|
||||
$ruleResult | Should -BeNullOrEmpty;
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
$ruleResult.TargetName | Should -BeIn 'clusterB';
|
||||
|
||||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
|
|
|
@ -259,8 +259,12 @@ Describe 'Azure.Storage' -Tag Storage {
|
|||
# Pass
|
||||
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
|
||||
$ruleResult | Should -Not -BeNullOrEmpty;
|
||||
$ruleResult.Length | Should -Be 2;
|
||||
$ruleResult.TargetName | Should -BeIn 'storage1', 'storage1/default/arm';
|
||||
$ruleResult.Length | Should -Be 1;
|
||||
@($ruleResult[0].TargetObject.resources | Where-Object {
|
||||
$_.type -eq 'Microsoft.Storage/storageAccounts/blobServices/containers' -and
|
||||
$_.name -eq 'storage1/default/arm'
|
||||
}).Length | Should -Be 1
|
||||
# $ruleResult.TargetName | Should -BeIn 'storage1', 'storage1/default/arm';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
<None Update="Resources.FrontDoor.Template.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources.KeyVault.Template.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources.Parameters.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
|
@ -6,6 +6,14 @@
|
|||
"defaultValue": "clusterA",
|
||||
"type": "string"
|
||||
},
|
||||
"clusterName2": {
|
||||
"defaultValue": "clusterB",
|
||||
"type": "string"
|
||||
},
|
||||
"clusterName3": {
|
||||
"defaultValue": "clusterC",
|
||||
"type": "string"
|
||||
},
|
||||
"clusterLocation": {
|
||||
"defaultValue": "[resourceGroup().location]",
|
||||
"type": "string"
|
||||
|
@ -100,12 +108,73 @@
|
|||
"osType": "Linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.ContainerService/managedClusters",
|
||||
"apiVersion": "2019-10-01",
|
||||
"name": "[parameters('clusterName2')]",
|
||||
"location": "[parameters('clusterLocation')]",
|
||||
"identity": {
|
||||
"type": "SystemAssigned"
|
||||
},
|
||||
"properties": {
|
||||
"kubernetesVersion": "1.19.7",
|
||||
"dnsPrefix": "[concat('dns-', parameters('clusterName'))]",
|
||||
"agentPoolProfiles": [
|
||||
{
|
||||
"name": "agentpool1",
|
||||
"count": 3,
|
||||
"vmSize": "Standard_B2ms",
|
||||
"osDiskSizeGB": 100,
|
||||
"vnetSubnetID": "[concat(parameters('vnetId'), '/subnets/subnet-01')]",
|
||||
"maxPods": 50,
|
||||
"type": "VirtualMachineScaleSets",
|
||||
"osType": "Linux"
|
||||
}
|
||||
],
|
||||
"servicePrincipalProfile": {
|
||||
"clientId": "00000000-0000-0000-0000-000000000000"
|
||||
},
|
||||
"addonProfiles": {
|
||||
"aciConnectorLinux": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"SubnetName": "subnet-aci"
|
||||
}
|
||||
},
|
||||
"azurePolicy": {
|
||||
"enabled": false
|
||||
},
|
||||
"httpApplicationRouting": {
|
||||
"enabled": true
|
||||
},
|
||||
"kubeDashboard": {
|
||||
"enabled": true
|
||||
},
|
||||
"omsagent": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"logAnalyticsWorkspaceResourceID": "[parameters('workspaceId')]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enableRBAC": true,
|
||||
"enablePodSecurityPolicy": false,
|
||||
"networkProfile": {
|
||||
"networkPlugin": "azure",
|
||||
"networkPolicy": "azure",
|
||||
"loadBalancerSku": "Standard",
|
||||
"serviceCidr": "192.168.0.0/16",
|
||||
"dnsServiceIP": "192.168.0.4",
|
||||
"dockerBridgeCidr": "172.17.0.1/16"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Microsoft.ContainerService/managedClusters/agentPools",
|
||||
"apiVersion": "2019-10-01",
|
||||
"name": "[concat(parameters('clusterName'), '/agentpool3')]",
|
||||
"name": "[concat(parameters('clusterName3'), '/agentpool3')]",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName'))]"
|
||||
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName3'))]"
|
||||
],
|
||||
"properties": {
|
||||
"count": 3,
|
||||
|
@ -121,9 +190,9 @@
|
|||
{
|
||||
"type": "Microsoft.ContainerService/managedClusters/agentPools",
|
||||
"apiVersion": "2019-10-01",
|
||||
"name": "[concat(parameters('clusterName'), '/agentpool4')]",
|
||||
"name": "[concat(parameters('clusterName3'), '/agentpool4')]",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName'))]"
|
||||
"[resourceId('Microsoft.ContainerService/managedClusters', parameters('clusterName3'))]"
|
||||
],
|
||||
"properties": {
|
||||
"count": 3,
|
||||
|
|
|
@ -203,10 +203,10 @@
|
|||
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ContainerService/managedClusters/cluster-D",
|
||||
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ContainerService/managedClusters/cluster-D",
|
||||
"Identity": {
|
||||
"PrincipalId": "00000000-0000-0000-0000-000000000000",
|
||||
"TenantId": "00000000-0000-0000-0000-000000000000",
|
||||
"Type": "SystemAssigned",
|
||||
"UserAssignedIdentities": null
|
||||
"PrincipalId": "00000000-0000-0000-0000-000000000000",
|
||||
"TenantId": "00000000-0000-0000-0000-000000000000",
|
||||
"Type": "SystemAssigned",
|
||||
"UserAssignedIdentities": null
|
||||
},
|
||||
"Kind": null,
|
||||
"Location": "region",
|
||||
|
@ -217,132 +217,170 @@
|
|||
"ParentResource": null,
|
||||
"Plan": null,
|
||||
"Properties": {
|
||||
"provisioningState": "Succeeded",
|
||||
"kubernetesVersion": "1.19.7",
|
||||
"dnsPrefix": "cluster-D",
|
||||
"fqdn": "cluster-D-nnnnnnnn.hcp.region.azmk8s.io",
|
||||
"agentPoolProfiles": [
|
||||
{
|
||||
"name": "agentpool",
|
||||
"count": 1,
|
||||
"vmSize": "Standard_B2ms",
|
||||
"osDiskSizeGB": 100,
|
||||
"vnetSubnetID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-A/subnets/subnet-A",
|
||||
"maxPods": 50,
|
||||
"type": "VirtualMachineScaleSets",
|
||||
"provisioningState": "Succeeded",
|
||||
"orchestratorVersion": "1.19.7",
|
||||
"nodeLabels": {},
|
||||
"mode": "System",
|
||||
"osType": "Linux",
|
||||
"nodeImageVersion": "AKSUbuntu:1604:2020.05.13"
|
||||
}
|
||||
],
|
||||
"windowsProfile": {
|
||||
"adminUsername": "azureuser"
|
||||
},
|
||||
"servicePrincipalProfile": {
|
||||
"clientId": "msi"
|
||||
},
|
||||
"addonProfiles": {
|
||||
"aciConnectorLinux": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"SubnetName": "subnet-B"
|
||||
},
|
||||
"identity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aciconnectorlinux-cluster-D",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
},
|
||||
"azurePolicy": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"version": "v2"
|
||||
},
|
||||
"identity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/azurepolicy-cluster-D",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
},
|
||||
"httpApplicationRouting": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"HTTPApplicationRoutingZoneName": "dca47a62fed041939528.region.aksapp.io"
|
||||
},
|
||||
"identity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/httpapplicationrouting-cluster-D",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
},
|
||||
"kubeDashboard": {
|
||||
"enabled": false,
|
||||
"config": null
|
||||
},
|
||||
"omsagent": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"logAnalyticsWorkspaceResourceID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/defaultresourcegroup-eus/providers/microsoft.operationalinsights/workspaces/defaultworkspace-00000000-0000-0000-0000-000000000000-region"
|
||||
},
|
||||
"identity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/omsagent-cluster-D",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nodeResourceGroup": "MC_test-rg_cluster-D_region",
|
||||
"enableRBAC": true,
|
||||
"enablePodSecurityPolicy": true,
|
||||
"networkProfile": {
|
||||
"networkPlugin": "azure",
|
||||
"networkPolicy": "azure",
|
||||
"loadBalancerSku": "Standard",
|
||||
"loadBalancerProfile": {
|
||||
"managedOutboundIPs": {
|
||||
"count": 1
|
||||
},
|
||||
"effectiveOutboundIPs": [
|
||||
"provisioningState": "Succeeded",
|
||||
"kubernetesVersion": "1.19.7",
|
||||
"dnsPrefix": "cluster-D",
|
||||
"fqdn": "cluster-D-nnnnnnnn.hcp.region.azmk8s.io",
|
||||
"agentPoolProfiles": [
|
||||
{
|
||||
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MC_test-rg_cluster-D_region/providers/Microsoft.Network/publicIPAddresses/00000000-0000-0000-0000-000000000000"
|
||||
"name": "agentpool",
|
||||
"count": 1,
|
||||
"vmSize": "Standard_B2ms",
|
||||
"osDiskSizeGB": 100,
|
||||
"vnetSubnetID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-A/subnets/subnet-A",
|
||||
"maxPods": 50,
|
||||
"type": "VirtualMachineScaleSets",
|
||||
"provisioningState": "Succeeded",
|
||||
"orchestratorVersion": "1.19.7",
|
||||
"nodeLabels": {},
|
||||
"mode": "System",
|
||||
"osType": "Linux",
|
||||
"nodeImageVersion": "AKSUbuntu:1604:2020.05.13"
|
||||
}
|
||||
]
|
||||
],
|
||||
"windowsProfile": {
|
||||
"adminUsername": "azureuser"
|
||||
},
|
||||
"serviceCidr": "192.168.0.0/16",
|
||||
"dnsServiceIP": "192.168.0.4",
|
||||
"dockerBridgeCidr": "172.17.0.1/16",
|
||||
"outboundType": "loadBalancer"
|
||||
},
|
||||
"aadProfile": {
|
||||
"managed": true,
|
||||
"adminGroupObjectIDs": null,
|
||||
"tenantID": "00000000-0000-0000-0000-000000000000"
|
||||
},
|
||||
"maxAgentPools": 10,
|
||||
"identityProfile": {
|
||||
"kubeletidentity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cluster-D-agentpool",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
"servicePrincipalProfile": {
|
||||
"clientId": "msi"
|
||||
},
|
||||
"addonProfiles": {
|
||||
"aciConnectorLinux": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"SubnetName": "subnet-B"
|
||||
},
|
||||
"identity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aciconnectorlinux-cluster-D",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
},
|
||||
"azurePolicy": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"version": "v2"
|
||||
},
|
||||
"identity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/azurepolicy-cluster-D",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
},
|
||||
"httpApplicationRouting": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"HTTPApplicationRoutingZoneName": "dca47a62fed041939528.region.aksapp.io"
|
||||
},
|
||||
"identity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/httpapplicationrouting-cluster-D",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
},
|
||||
"kubeDashboard": {
|
||||
"enabled": false,
|
||||
"config": null
|
||||
},
|
||||
"omsagent": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"logAnalyticsWorkspaceResourceID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/defaultresourcegroup-eus/providers/microsoft.operationalinsights/workspaces/defaultworkspace-00000000-0000-0000-0000-000000000000-region"
|
||||
},
|
||||
"identity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/omsagent-cluster-D",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nodeResourceGroup": "MC_test-rg_cluster-D_region",
|
||||
"enableRBAC": true,
|
||||
"enablePodSecurityPolicy": true,
|
||||
"networkProfile": {
|
||||
"networkPlugin": "azure",
|
||||
"networkPolicy": "azure",
|
||||
"loadBalancerSku": "Standard",
|
||||
"loadBalancerProfile": {
|
||||
"managedOutboundIPs": {
|
||||
"count": 1
|
||||
},
|
||||
"effectiveOutboundIPs": [
|
||||
{
|
||||
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/MC_test-rg_cluster-D_region/providers/Microsoft.Network/publicIPAddresses/00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
]
|
||||
},
|
||||
"serviceCidr": "192.168.0.0/16",
|
||||
"dnsServiceIP": "192.168.0.4",
|
||||
"dockerBridgeCidr": "172.17.0.1/16",
|
||||
"outboundType": "loadBalancer"
|
||||
},
|
||||
"aadProfile": {
|
||||
"managed": true,
|
||||
"adminGroupObjectIDs": null,
|
||||
"tenantID": "00000000-0000-0000-0000-000000000000"
|
||||
},
|
||||
"maxAgentPools": 10,
|
||||
"identityProfile": {
|
||||
"kubeletidentity": {
|
||||
"resourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/MC_test-rg_cluster-D_region/providers/Microsoft.ManagedIdentity/userAssignedIdentities/cluster-D-agentpool",
|
||||
"clientId": "00000000-0000-0000-0000-000000000000",
|
||||
"objectId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ResourceGroupName": "test-rg",
|
||||
"Type": "Microsoft.ContainerService/managedClusters",
|
||||
"ResourceType": "Microsoft.ContainerService/managedClusters",
|
||||
"ExtensionResourceType": null,
|
||||
"Sku": {
|
||||
"Name": "Basic",
|
||||
"Tier": "Free",
|
||||
"Size": null,
|
||||
"Family": null,
|
||||
"Model": null,
|
||||
"Capacity": null
|
||||
"Name": "Basic",
|
||||
"Tier": "Free",
|
||||
"Size": null,
|
||||
"Family": null,
|
||||
"Model": null,
|
||||
"Capacity": null
|
||||
},
|
||||
"Tags": null,
|
||||
"SubscriptionId": "00000000-0000-0000-0000-000000000000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ContainerService/managedClusters/cluster-E/agentPools/system",
|
||||
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.ContainerService/managedClusters/cluster-E/agentPools/system",
|
||||
"Identity": null,
|
||||
"Kind": null,
|
||||
"Location": null,
|
||||
"ManagedBy": null,
|
||||
"ResourceName": "system",
|
||||
"Name": "system",
|
||||
"ExtensionResourceName": null,
|
||||
"ParentResource": "managedClusters/cluster-E",
|
||||
"Properties": {
|
||||
"count": 2,
|
||||
"vmSize": "Standard_D2s_v3",
|
||||
"osDiskSizeGB": 32,
|
||||
"osDiskType": "Ephemeral",
|
||||
"vnetSubnetID": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.Network/virtualNetworks/vnet-A/subnets/subnet-A",
|
||||
"maxPods": 50,
|
||||
"type": "VirtualMachineScaleSets",
|
||||
"maxCount": 2,
|
||||
"minCount": 2,
|
||||
"enableAutoScaling": true,
|
||||
"provisioningState": "Succeeded",
|
||||
"powerState": {
|
||||
"code": "Running"
|
||||
},
|
||||
"orchestratorVersion": "1.19.7",
|
||||
"nodeLabels": {},
|
||||
"mode": "System",
|
||||
"osType": "Linux",
|
||||
"nodeImageVersion": "AKSUbuntu-1804containerd-2021.01.28"
|
||||
},
|
||||
"ResourceGroupName": "test-rg",
|
||||
"Type": "Microsoft.ContainerService/managedClusters/agentPools",
|
||||
"ResourceType": "Microsoft.ContainerService/managedClusters/agentPools",
|
||||
"Sku": null,
|
||||
"Tags": null
|
||||
}
|
||||
]
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"parameters": {
|
||||
"vaultName": {
|
||||
"type": "string",
|
||||
"defaultValue": "keyvault1",
|
||||
"metadata": {
|
||||
"description": "Required. The name of the Key Vault."
|
||||
}
|
||||
|
@ -91,7 +92,7 @@
|
|||
},
|
||||
"workspaceId": {
|
||||
"type": "string",
|
||||
"defaultValue": "",
|
||||
"defaultValue": "/subscriptions/workspaceId",
|
||||
"metadata": {
|
||||
"description": "Optional. The workspace to store audit logs."
|
||||
}
|
||||
|
@ -129,30 +130,48 @@
|
|||
"enableSoftDelete": "[parameters('useSoftDelete')]",
|
||||
"enablePurgeProtection": "[parameters('usePurgeProtection')]"
|
||||
},
|
||||
"tags": "[parameters('tags')]",
|
||||
"resources": [
|
||||
{
|
||||
"comments": "Enable monitoring of Key Vault operations.",
|
||||
"condition": "[not(empty(parameters('workspaceId')))]",
|
||||
"type": "Microsoft.KeyVault/vaults/providers/diagnosticSettings",
|
||||
"name": "[concat(parameters('vaultName'), '/Microsoft.Insights/service')]",
|
||||
"apiVersion": "2016-09-01",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.KeyVault/vaults/', parameters('vaultName'))]"
|
||||
],
|
||||
"properties": {
|
||||
|
||||
"workspaceId": "[parameters('workspaceId')]",
|
||||
"logs": [
|
||||
{
|
||||
"category": "AuditEvent",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
"tags": "[parameters('tags')]"
|
||||
},
|
||||
{
|
||||
"comments": "Enable monitoring of Key Vault operations.",
|
||||
"condition": "[not(empty(parameters('workspaceId')))]",
|
||||
"type": "Microsoft.KeyVault/vaults/providers/diagnosticSettings",
|
||||
"name": "[concat(parameters('vaultName'), '/Microsoft.Insights/service')]",
|
||||
"apiVersion": "2016-09-01",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.KeyVault/vaults/', parameters('vaultName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"workspaceId": "[parameters('workspaceId')]",
|
||||
"logs": [
|
||||
{
|
||||
"category": "AuditEvent",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"comments": "Enable monitoring of Key Vault operations.",
|
||||
"condition": "[not(empty(parameters('workspaceId')))]",
|
||||
"scope": "Microsoft.KeyVault/vaults/keyvault1",
|
||||
"type": "Microsoft.Insights/diagnosticsettings",
|
||||
"name": "monitor",
|
||||
"apiVersion": "2016-09-01",
|
||||
"location": "[parameters('location')]",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.KeyVault/vaults/', parameters('vaultName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"workspaceId": "[parameters('workspaceId')]",
|
||||
"logs": [
|
||||
{
|
||||
"category": "AuditEvent",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
|
|
|
@ -6,6 +6,7 @@ using Newtonsoft.Json.Linq;
|
|||
using PSRule.Rules.Azure.Data.Template;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
using static PSRule.Rules.Azure.Data.Template.TemplateVisitor;
|
||||
|
||||
|
@ -119,6 +120,44 @@ namespace PSRule.Rules.Azure
|
|||
Assert.Equal("my-nic", resources[3]["name"].Value<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestedResources()
|
||||
{
|
||||
var resources = ProcessTemplate(GetSourcePath("Resources.KeyVault.Template.json"), null);
|
||||
Assert.NotNull(resources);
|
||||
Assert.Single(resources);
|
||||
|
||||
var subResources = resources[0]["resources"].Value<JArray>();
|
||||
Assert.Equal(2, subResources.Count);
|
||||
Assert.Equal("keyvault1/Microsoft.Insights/service", subResources[0]["name"].Value<string>());
|
||||
Assert.Equal("monitor", subResources[1]["name"].Value<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParentResourceId()
|
||||
{
|
||||
const string expected1 = "Microsoft.ServiceBus/namespaces/besubns";
|
||||
const string expected2 = "Microsoft.KeyVault/vaults/keyvault1";
|
||||
|
||||
var actual = JObject.Parse("{ \"type\": \"Microsoft.ServiceBus/namespaces/topics\", \"name\": \"besubns/demo1\" }");
|
||||
TemplateContext.TryParentResourceId(actual, out string[] resourceId);
|
||||
Assert.Equal(expected1, resourceId[0]);
|
||||
|
||||
actual = JObject.Parse("{ \"type\": \"Microsoft.KeyVault/vaults\", \"name\": \"keyvault1\" }");
|
||||
TemplateContext.TryParentResourceId(actual, out resourceId);
|
||||
Assert.Empty(resourceId);
|
||||
|
||||
actual = JObject.Parse("{ \"type\": \"Microsoft.KeyVault/vaults/providers/diagnosticsettings\", \"name\": \"keyvault1/Microsoft.Insights/service\" }");
|
||||
TemplateContext.TryParentResourceId(actual, out resourceId);
|
||||
Assert.Equal(expected2, resourceId[0]);
|
||||
|
||||
actual = JObject.Parse("{ \"type\": \"Microsoft.Insights/diagnosticsettings\", \"name\": \"auditing-storage\", \"scope\": \"Microsoft.KeyVault/vaults/keyvault1\" }");
|
||||
TemplateContext.TryParentResourceId(actual, out resourceId);
|
||||
Assert.Equal(expected2, resourceId[0]);
|
||||
}
|
||||
|
||||
#region Helper methods
|
||||
|
||||
private static string GetSourcePath(string fileName)
|
||||
{
|
||||
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
|
||||
|
@ -132,7 +171,7 @@ namespace PSRule.Rules.Azure
|
|||
var context = new TemplateContext();
|
||||
context.Load(parametersObject);
|
||||
visitor.Visit(context, "deployment", templateObject);
|
||||
return context.Resources.ToArray();
|
||||
return context.GetResources().Select(i => i.Value).ToArray();
|
||||
}
|
||||
|
||||
private static T ReadFile<T>(string path)
|
||||
|
@ -142,5 +181,7 @@ namespace PSRule.Rules.Azure
|
|||
|
||||
return JsonConvert.DeserializeObject<T>(File.ReadAllText(path));
|
||||
}
|
||||
|
||||
#endregion Helper methods
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче