source/Public/New-GuestConfigurationPolicy.ps1 (404 lines of code) (raw):
<#
.SYNOPSIS
Creates a policy definition to monitor and remediate settings on machines through
Azure Guest Configuration and Azure Policy.
.PARAMETER DisplayName
The display name of the policy to create.
The display name has a maximum length of 128 characters.
.PARAMETER Description
The description of the policy to create.
The display name has a maximum length of 512 characters.
.PARAMETER PolicyId
The unique GUID of the policy definition.
If you are trying to update an existing policy definition, then this ID must match the 'name'
field in the existing definition.
You can run New-Guid to generate a new GUID.
.PARAMETER PolicyVersion
The version of the policy definition.
If you are updating an existing policy definition, then this version should be greater than
the value in the 'metadata.version' field in the existing definition.
Note: This is NOT the version of the Guest Configuration package.
You can validate the Guest Configuration package version via the ContentVersion parameter.
.PARAMETER ContentUri
The public HTTP or HTTPS URI of the Guest Configuration package (.zip) to run via the created policy.
Example: https://github.com/azure/auditservice/release/AuditService.zip
Note: If you are using an Azure storage account to store the custom machine configuration package artifact, you have two options for access:
1. Generate a blob shared access signature (SAS) token with read access and provide the full blob URI with the SAS token for the ContentUri parameter.
2. Create a user-assigned managed identity with read access to the storage account blob containing the package.
Provide the resource ID of the managed identity, a local path to the zipped package, a URI to the package without a SAS token and the ExcludeArcMachines parameter.
With this option, once the generated policy is applied, the managed identity will be used to download the package onto the target machine.
.PARAMETER ManagedIdentityResourceId
This is the identity that is used to download the package from storage account container instead of using SaS url.
The value for this parameter needs to be the resource id of the managed identity.
This is an option to use when the package is stored in a storage account and the storage account is protected by a managed identity.
Note: optional parameter. If this is specified, LocalContentPath and ExcludeArcMachines must also be specified.
.PARAMETER LocalContentPath
This is the path to the local package zip file. This is used to calculate the hash of the package.
The value of this parameter is not used in the policy definition.
Note: optional parameter. If this is specified, ManagedIdentityResourceId must also be specified.
.PARAMETER ContentVersion
If specified, the version of the Guest Configuration package (.zip) downloaded via the
content URI must match this value.
This is for validation only.
Note: This is NOT the version of the policy definition.
You can define the policy definition version via the PolicyVersion parameter.
.PARAMETER Path
The path to the folder under which to create the new policy definition file.
The default value is the 'definitions' folder under your GuestConfiguration module path.
.PARAMETER Platform
The target platform (Windows or Linux) for the policy.
The default value is Windows.
.PARAMETER Parameter
The parameters to expose on the policy.
All parameters passed to the policy must be single string values.
Example:
$policyParameters = @(
@{
Name = 'ServiceName' # Required
DisplayName = 'Windows Service Name' # Required
Description = 'Name of the windows service to be audited.' # Required
ResourceType = 'Service' # Required
ResourceId = 'windowsService' # Required
ResourcePropertyName = 'Name' # Required
DefaultValue = 'winrm' # Optional
AllowedValues = @('wscsvc', 'WSearch', 'wcncsvc', 'winrm') # Optional
},
@{
Name = 'ServiceState' # Required
DisplayName = 'Windows Service State' # Required
Description = 'State of the windows service to be audited.' # Required
ResourceType = 'Service' # Required
ResourceId = 'windowsService' # Required
ResourcePropertyName = 'State' # Required
DefaultValue = 'Running' # Optional
AllowedValues = @('Running', 'Disabled') # Optional
}
)
.PARAMETER Mode
Defines the mode under which this policy should run the package on the machine.
Allowed modes:
Audit: Monitors the machine only. Will not make modifications to the machine.
ApplyAndMonitor: Modifies the machine once if it does not match the expected state.
Then monitors the machine only until another remediation task is triggered via Azure Policy.
Will make modifications to the machine.
ApplyAndAutoCorrect: Modifies the machine any time it does not match the expected state.
You will need trigger a remediation task via Azure Policy to start modifications the first time.
Will make modifications to the machine.
The default value is Audit.
If the package has been created as Audit-only, you cannot create an Apply policy with that package.
The package will need to be re-created in AuditAndSet mode.
.PARAMETER Tag
A hashtable of the tags that should be on machines to apply this policy on.
If this is specified, the created policy will only be applied to machines with all the specified tags.
.PARAMETER ExcludeArcMachines
This parameter needs to be specified if the New-GuestConfigurationPolicy is using a User Assigned Identity.
Enabling this parameter will signal that users are aware of exclusion of Arc enabled servers in the definition.
.EXAMPLE
New-GuestConfigurationPolicy `
-ContentUri https://github.com/azure/auditservice/release/AuditService.zip `
-DisplayName 'Monitor Windows Service Policy.' `
-Description 'Policy to monitor service on Windows machine.' `
-PolicyVersion 1.1.0 `
-Path ./git/custom_policy `
-Tag @{ Owner = 'WebTeam' }
.EXAMPLE
$PolicyParameterInfo = @(
@{
Name = 'ServiceName' # Policy parameter name (mandatory)
DisplayName = 'windows service name.' # Policy parameter display name (mandatory)
Description = "Name of the windows service to be audited." # Policy parameter description (mandatory)
ResourceType = "Service" # configuration resource type (mandatory)
ResourceId = 'windowsService' # configuration resource property name (mandatory)
ResourcePropertyName = "Name" # configuration resource property name (mandatory)
DefaultValue = 'winrm' # Policy parameter default value (optional)
AllowedValues = @('wscsvc','WSearch','wcncsvc','winrm') # Policy parameter allowed values (optional)
}
)
New-GuestConfigurationPolicy -ContentUri 'https://github.com/azure/auditservice/release/AuditService.zip' `
-DisplayName 'Monitor Windows Service Policy.' `
-Description 'Policy to monitor service on Windows machine.' `
-PolicyId $myPolicyGuid `
-PolicyVersion 2.4.0 `
-Path ./policyDefinitions `
-Parameter $PolicyParameterInfo
.OUTPUTS
Returns the name and path of the Guest Configuration policy definition.
This output can then be piped into New-AzPolicyDefinition.
@{
PSTypeName = 'GuestConfiguration.Policy'
Name = $policyName
Path = $Path
}
#>
function New-GuestConfigurationPolicy
{
[CmdletBinding()]
[OutputType([PSCustomObject])]
param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$DisplayName,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.String]
$Description,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Guid]
$PolicyId,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Version]
$PolicyVersion,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[System.Uri]
$ContentUri,
[Parameter(ParameterSetName='ManagedIdentity')]
[System.String]
$ManagedIdentityResourceId,
[Parameter(ParameterSetName='ManagedIdentity')]
[System.String]
$LocalContentPath,
[Parameter()]
[System.Version]
$ContentVersion,
[Parameter()]
[ValidateNotNullOrEmpty()]
[System.String]
$Path,
[Parameter()]
[ValidateSet('Windows', 'Linux')]
[System.String]
$Platform = 'Windows',
[Parameter()]
[System.Collections.Hashtable[]]
$Parameter,
[Parameter()]
[ValidateSet('Audit', 'ApplyAndAutoCorrect', 'ApplyAndMonitor')]
[System.String]
$Mode = 'Audit',
[Parameter()]
[System.Collections.Hashtable]
$Tag,
[Parameter()]
[System.Boolean]
$IncludeVMSS = $true,
[Parameter()]
[Switch]
$ExcludeArcMachines
)
# Validate parameters
if ($DisplayName.Length -gt 128)
{
throw "The specified display name is more than the limit of 128 characters. Please specify a shorter display name."
}
if ($Description.Length -gt 512)
{
throw "The specified description is more than the limit of 512 characters. Please specify a shorter description."
}
if ($null -eq $ContentUri.AbsoluteURI)
{
throw "The specified package URI is not an absolute URI. Please specify a valid HTTP or HTTPS URI with the ContentUri parameter."
}
if ($ContentUri.Scheme -notmatch '[http|https]')
{
throw "The specified package URI does not follow the HTTP or HTTPS scheme. Please specify a valid HTTP or HTTPS URI with the ContentUri parameter."
}
if ($PSCmdlet.ParameterSetName -eq 'ManagedIdentity' -and ([string]::IsNullOrWhiteSpace($ManagedIdentityResourceId) -or [string]::IsNullOrWhiteSpace($LocalContentPath)))
{
throw "Both ManagedIdentityResourceId and LocalContentPath must be provided together. Please include ManagedIdentityResourceId, LocalContentPath, and ExcludeArcMachines parameters."
}
$requiredParameterProperties = @('Name', 'DisplayName', 'Description', 'ResourceType', 'ResourceId', 'ResourcePropertyName')
$defaultValueParameterProperty = 'DefaultValue'
$allowedValuesParameterProperty = 'AllowedValues'
foreach ($parameterInfo in $Parameter)
{
foreach ($requiredParameterProperty in $requiredParameterProperties)
{
if (-not ($parameterInfo.Keys -contains $requiredParameterProperty))
{
$requiredParameterPropertyString = $requiredParameterProperties -join ', '
throw "One of the specified policy parameters is missing the mandatory property '$requiredParameterProperty'. The mandatory properties for parameters are: $requiredParameterPropertyString"
}
if ($parameterInfo[$requiredParameterProperty] -isnot [string])
{
throw "The mandatory property '$requiredParameterProperty' of one of the specified parameters is not a string. All mandatory property values of a parameter must be strings."
}
}
$parameterName = $parameterInfo['Name']
if ($parameterInfo.Keys -contains $defaultValueParameterProperty)
{
if ($parameterInfo[$defaultValueParameterProperty] -isnot [string] -and
$parameterInfo[$defaultValueParameterProperty] -isnot [boolean] -and
$parameterInfo[$defaultValueParameterProperty] -isnot [int] -and
$parameterInfo[$defaultValueParameterProperty] -isnot [double])
{
throw "The property '$defaultValueParameterProperty' of parameter '$parameterName' is not a string, boolean, integer, or double."
}
}
if ($parameterInfo.Keys -contains $allowedValuesParameterProperty)
{
if ($parameterInfo[$allowedValuesParameterProperty] -isnot [array])
{
throw "The property '$allowedValuesParameterProperty' of parameter '$parameterName' is not an array."
}
foreach ($allowedValue in $parameterInfo[$allowedValuesParameterProperty])
{
if ($allowedValue -isnot [string] -and
$allowedValue -isnot [boolean] -and
$allowedValue -isnot [int] -and
$allowedValue -isnot [double])
{
throw "One of the values in the array for property '$allowedValuesParameterProperty' of parameter '$parameterName' is not a string, boolean, integer, or double."
}
}
}
}
# Download package
$tempPath = Reset-GCWorkerTempDirectory
$packagePath = Join-Path -Path $tempPath -ChildPath 'extracted'
if (-not ([string]::IsNullOrWhiteSpace($ManagedIdentityResourceId) -or [string]::IsNullOrWhiteSpace($LocalContentPath)))
{
if (-not $ExcludeArcMachines)
{
throw "The ManagedIdentityResourceId and LocalContentPath parameters are defined but the -ExcludeArcMachines parameter is not. Managed identities cannot be used with Azure Arc machines. Please provide the -ExcludeArcMachines parameter to exclude Azure Arc machines and use a managed identity with this policy."
}
$packageFileDownloadPath = $LocalContentPath
}
else
{
$packageFileDownloadName = 'temp.zip'
$packageFileDownloadPath = Join-Path -Path $tempPath -ChildPath $packageFileDownloadName
if (Test-Path -Path $packageFileDownloadPath)
{
$null = Remove-Item -Path $packageFileDownloadPath -Force
}
$null = Invoke-WebRequest -Uri $ContentUri -OutFile $packageFileDownloadPath
}
if ($null -eq (Get-Command -Name 'Get-FileHash' -ErrorAction 'SilentlyContinue'))
{
$null = Import-Module -Name 'Microsoft.PowerShell.Utility'
}
$contentHash = (Get-FileHash -Path $packageFileDownloadPath -Algorithm 'SHA256').Hash
# Extract package
$null = Expand-Archive -Path $packageFileDownloadPath -DestinationPath $packagePath -Force
# Get configuration name
$mofFilePattern = '*.mof'
$mofChildItems = @( Get-ChildItem -Path $packagePath -Filter $mofFilePattern -File )
if ($mofChildItems.Count -eq 0)
{
throw "No .mof file found in the extracted Guest Configuration package at '$packagePath'. The Guest Configuration package must include a compiled DSC configuration (.mof) with the same name as the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
}
elseif ($mofChildItems.Count -gt 1)
{
throw "Found more than one .mof file in the extracted Guest Configuration package at '$packagePath'. Please remove any extra .mof files from the root of the package. Please use the New-GuestConfigurationPackage cmdlet to generate a valid package."
}
$mofFile = $mofChildItems[0]
$packageName = $mofFile.BaseName
# Get package version
$packageVersion = '1.0.0'
$metaconfigFileName = "{0}.metaconfig.json" -f $packageName
$metaconfigFilePath =Join-Path -Path $packagePath -ChildPath $metaconfigFileName
if (Test-Path -Path $metaconfigFilePath)
{
$metaconfig = Get-Content -Path $metaconfigFilePath -Raw | ConvertFrom-Json | ConvertTo-OrderedHashtable
if ($metaconfig.Keys -contains 'Version')
{
$packageVersion = $metaconfig['Version']
Write-Verbose -Message "Downloaded package has the version $packageVersion"
if ($null -ne $ContentVersion -and $ContentVersion -ne $packageVersion)
{
throw "Downloaded package version ($packageVersion) does not match specfied content version ($ContentVersion)."
}
}
else
{
if ($null -eq $ContentVersion)
{
Write-Warning -Message "Failed to determine the package version from the metaconfig file '$metaconfigFileName' in the downloaded package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
}
else
{
throw "Failed to determine the package version from the metaconfig file '$metaconfigFileName' in the downloaded package. Package version does not match specfied content version ($ContentVersion). Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
}
}
if ($metaconfig.Keys -contains 'Type')
{
$packageType = $metaconfig['Type']
Write-Verbose -Message "Downloaded package has the type $packageType"
if ($packageType -eq 'Audit' -and $Mode -ne 'Audit')
{
throw 'The specified package has been marked as Audit-only. You cannot create an Apply policy with an Audit-only package. Please change the mode of the policy or the type of the package.'
}
}
else
{
Write-Warning -Message "Failed to determine the package type from the metaconfig file '$metaconfigFileName' in the downloaded package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
}
}
else
{
Write-Warning -Message "Failed to find the metaconfig file '$metaconfigFileName' in the downloaded package. Please use the latest version of the New-GuestConfigurationPackage cmdlet to construct your package."
}
# Determine paths
if ([String]::IsNullOrEmpty($Path))
{
$Path = Get-Location
}
$Path = Resolve-RelativePath -Path $Path
if (-not (Test-Path -Path $Path))
{
$null = New-Item -Path $Path -ItemType 'Directory' -Force
}
# Determine if policy is AINE or DINE
if ($Mode -eq 'Audit')
{
$fileName = '{0}_AuditIfNotExists.json' -f $packageName
}
else
{
$fileName = '{0}_DeployIfNotExists.json' -f $packageName
}
$filePath = Join-Path -Path $Path -ChildPath $fileName
if (Test-Path -Path $filePath)
{
$null = Remove-Item -Path $filePath -Force
}
# Generate definition
$policyDefinitionContentParameters = @{
DisplayName = $DisplayName
Description = $Description
PolicyVersion = $PolicyVersion
ConfigurationName = $packageName
ConfigurationVersion = $packageVersion
ContentUri = $ContentUri
ContentHash = $contentHash
Platform = $Platform
AssignmentType = $Mode
PolicyId = $PolicyId
Parameter = $Parameter
Tag = $Tag
IncludeVMSS = $IncludeVMSS
}
if (-not [string]::IsNullOrWhiteSpace($ManagedIdentityResourceId))
{
$policyDefinitionContentParameters.ManagedIdentityResourceId = $ManagedIdentityResourceId
}
$policyDefinitionContent = New-GuestConfigurationPolicyContent @policyDefinitionContentParameters -ExcludeArcMachines:$ExcludeArcMachines
# Convert definition hashtable to JSON
$policyDefinitionContentJson = (ConvertTo-Json -InputObject $policyDefinitionContent -Depth 100).Replace('\u0027', "'")
$formattedPolicyDefinitionContentJson = Format-PolicyDefinitionJson -Json $policyDefinitionContentJson
# Write JSON to file
$null = Set-Content -Path $filePath -Value $formattedPolicyDefinitionContentJson -Encoding 'UTF8' -Force
# Return policy information
$result = [PSCustomObject]@{
PSTypeName = 'GuestConfiguration.Policy'
Name = $packageName
Path = $filePath
PolicyId = $PolicyId
}
return $result
}