From 407522c2452a4397988204325756e2e261e1e3d9 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 7 Feb 2025 04:00:41 +1000 Subject: [PATCH 01/10] Add documentation for App Control for Windows --- docs/docsite/rst/os_guide/intro_windows.rst | 1 + .../New-AnsiblePowerShellSignature.ps1 | 365 ++++++++++++++++++ .../rst/os_guide/windows_app_control.rst | 141 +++++++ 3 files changed, 507 insertions(+) create mode 100644 docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 create mode 100644 docs/docsite/rst/os_guide/windows_app_control.rst diff --git a/docs/docsite/rst/os_guide/intro_windows.rst b/docs/docsite/rst/os_guide/intro_windows.rst index fd626737b23..cf705a6588d 100644 --- a/docs/docsite/rst/os_guide/intro_windows.rst +++ b/docs/docsite/rst/os_guide/intro_windows.rst @@ -13,6 +13,7 @@ This is an index of all the topics covered in this guide. .. toctree:: :maxdepth: 1 + windows_app_control windows_dsc windows_performance windows_ssh diff --git a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 b/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 new file mode 100644 index 00000000000..7c908f57340 --- /dev/null +++ b/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 @@ -0,0 +1,365 @@ +#Requires -Module OpenAuthenticode + +using namespace System.Collections.Generic +using namespace System.IO +using namespace System.Management.Automation +using namespace System.Management.Automation.Language +using namespace System.Security.Cryptography.X509Certificates + +Function New-AnsiblePowerShellSignature { + <# + .SYNOPSIS + Creates and signed Ansible content for WDAC. + + .DESCRIPTION + This function will generate the powershell_signatures.ps1 manifest and sign + it. The manifest file includes all PowerShell/C# module_utils and + PowerShell modules in the collection(s) specified. It will also create the + '*.authenticode' signature files for any PowerShell scripts found in the + collections plugins/plugin_utils/powershell directories. + + .PARAMETER Certificate + The certificate to use for signing the content. + + .PARAMETER Collection + The collection(s) to sign. This is set to ansible.builtin by default but + can be overriden to include other collections like ansible.windows. + + .PARAMETER Skip + A list of plugins to skip by the fully qualified name. Plugins skipped will + be marked as unsupported in the manifest and will error when being run. + Scripts skipped are just ignored and will not have its '.authenticode' + signature file created. + + The values in the list should be the fully qualified name of the plugin as + referenced in Ansible. The value can also optionally include the extension + of the file if the FQN is ambigious, e.g. collection util that has both a + PowerShell and C# util of the same name. + + Here are some examples for the various content types: + + # Ansible Builtin Modules + 'ansible.builtin.module_name' + + # Ansible Builtin ModuleUtil + 'Ansible.ModuleUtils.PowerShellUtil' + 'Ansible.CSharpUtil' + + # Collection Modules + 'namespace.name.module_name' + + # Collection ModuleUtils + 'ansible_collections.namespace.name.plugins.module_utils.PowerShellUtil' + 'ansible_collections.namespace.name.plugins.module_utils.PowerShellUtil.psm1' + + 'ansible_collections.namespace.name.plugins.module_utils.CSharpUtil' + 'ansible_collections.namespace.name.plugins.module_utils.CSharpUtil.cs' + + # Collection Scripts + 'ansible_collections.namespace.name.plugins.plugin_utils.powershell.script' + + .PARAMETER TimeStampServer + Optional authenticode timestamp server to use when signing the content. + + .EXAMPLE + Signs just the content included in Ansible. + + $cert = [X509Certificate2]::new("wdac-cert.pfx", "password") + New-AnsiblePowerShellSignature -Certificate $cert + + .EXAMPLE + Signs just the content include in Ansible and the ansible.windows collection + + $cert = [X509Certificate2]::new("wdac-cert.pfx", "password") + New-AnsiblePowerShellSignature -Certificate $cert -Collection ansible.builtin, ansible.windows + + .EXAMPLE + Signs just the content in the ansible.windows collection + + $cert = [X509Certificate2]::new("wdac-cert.pfx", "password") + New-AnsiblePowerShellSignature -Certificate $cert -Collection ansible.windows + + .EXAMPLE + Signs content but skips the specified modules and module_utils + $skip = @( + # Skips the module specified + 'namespace.name.module' + + # Skips the module_utils specified + 'ansible_collections.namespace.name.plugins.module_utils.PowerShellUtil' + 'ansible_collections.namespace.name.plugins.module_utils.CSharpUtil' + + # Skips signing the file specified + 'ansible_collections.namespace.name.plugins.plugin_utils.powershell.file.ps1' + ) + $cert = [X509Certificate2]::new("wdac-cert.pfx", "password") + New-AnsiblePowerShellSignature -Certificate $cert -Collection namespace.name -Skip $skip + #> + [CmdletBinding()] + param ( + [Parameter( + Mandatory + )] + [X509Certificate2] + $Certificate, + + [Parameter( + ValueFromPipeline, + ValueFromPipelineByPropertyName + )] + [string[]] + $Collection = "ansible.builtin", + + [Parameter( + ValueFromPipelineByPropertyName + )] + [string[]] + $Skip = @(), + + [Parameter()] + [string] + $TimeStampServer + ) + + begin { + Write-Verbose "Attempting to get ansible-config dump" + $configRaw = ansible-config dump --format json --type base 2>&1 + if ($LASTEXITCODE) { + $err = [ErrorRecord]::new( + [Exception]::new("Failed to get Ansible configuration, RC: ${LASTEXITCODE} - $configRaw"), + 'FailedToGetAnsibleConfiguration', + [ErrorCategory]::NotSpecified, + $null) + $PSCmdlet.ThrowTerminatingError($err) + } + + $config = $configRaw | ConvertFrom-Json + $collectionsPaths = @($config | Where-Object name -EQ 'COLLECTIONS_PATHS' | ForEach-Object value) + Write-Verbose "Collections paths to be searched: [$($collectionsPaths -join ":")]" + + # FIXME: Don't hardcode this list + $Skip = @( + $Skip + + # Known to not work, requires more changes + 'ansible.windows.win_updates' + ) + + $signParams = @{ + Certificate = $Certificate + HashAlgorithm = 'SHA256' + } + if ($TimeStampServer) { + $signParams.TimeStampServer = $TimeStampServer + } + + $checked = [HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) + + Function New-HashEntry { + [OutputType([PSObject])] + [CmdletBinding()] + param ( + [Parameter(Mandatory, ValueFromPipeline)] + [FileInfo] + $File, + + [Parameter(Mandatory)] + [AllowEmptyString()] + [string] + $PluginBase, + + [Parameter()] + [AllowEmptyCollection()] + [string[]] + $Skip = @() + ) + + process { + $nameWithoutExt = [string]::IsNullOrEmpty($PluginBase) ? $File.BaseName : "$PluginBase.$($File.BaseName)" + $nameWithExt = "$nameWithoutExt$($File.Extension)" + + $mode = 'Trusted' + if ($nameWithoutExt -in $Skip -or $nameWithExt -in $Skip) { + Write-Verbose "Marking plugin '$nameWithExt' as unsupported as it is in the supplied skip list" + $mode = 'Unsupported' + } + + Write-Verbose "Hashing plugin '$nameWithExt'" + $hash = Get-FileHash -LiteralPath $File.FullName -Algorithm SHA256 + [PSCustomObject]@{ + Name = $nameWithExt + Hash = $hash.Hash + Mode = $mode + } + } + } + } + + process { + foreach ($c in $Collection) { + try { + if (-not $checked.Add($c)) { + Write-Verbose "Skipping already processed collection $c" + continue + } + + $metaPath = $null + $pathsToSign = [List[FileInfo]]::new() + $hashedPaths = [List[PSObject]]::new() + + if ($c -eq 'ansible.builtin') { + Write-Verbose "Attempting to get Ansible installation path" + $ansiblePath = python -c "import ansible; print(ansible.__file__)" 2>&1 + if ($LASTEXITCODE) { + throw "Failed to find Ansible installation path, RC: ${LASTEXITCODE} - $ansiblePath" + } + + $ansibleBase = Split-Path -Path $ansiblePath -Parent + $metaPath = [Path]::Combine($ansibleBase, 'config') + + $execWrapper = Get-Item -LiteralPath ([Path]::Combine($ansibleBase, 'executor', 'powershell', 'exec_wrapper.ps1')) + $pathsToSign.Add($execWrapper) + + $ansiblePwshContent = [PSObject[]]@( + # These are needed for Ansible and cannot be skipped + Get-ChildItem -Path ([Path]::Combine($ansibleBase, 'executor', 'powershell', '*.ps1')) -Exclude "bootstrap_wrapper.ps1" | + New-HashEntry -PluginBase "ansible.executor.powershell" + + # Builtin utils are special where the filename is their FQN + Get-ChildItem -Path ([Path]::Combine($ansibleBase, 'module_utils', 'csharp', '*.cs')) | + New-HashEntry -PluginBase "" -Skip $Skip + Get-ChildItem -Path ([Path]::Combine($ansibleBase, 'module_utils', 'powershell', '*.psm1')) | + New-HashEntry -PluginBase "" -Skip $Skip + + Get-ChildItem -Path ([Path]::Combine($ansibleBase, 'modules', '*.ps1')) | + New-HashEntry -PluginBase $c -Skip $Skip + ) + $hashedPaths.AddRange($ansiblePwshContent) + } + else { + Write-Verbose "Attempting to get collection path for $c" + $namespace, $name, $remaining = $c.ToLowerInvariant() -split '\.' + if (-not $name -or $remaining) { + throw "Invalid collection name '$c', must be in the format 'namespace.name'" + } + + $foundPath = $null + foreach ($path in $collectionsPaths) { + $collectionPath = [Path]::Combine($path, 'ansible_collections', $namespace, $name) + + Write-Verbose "Checking if collection $c exists in '$collectionPath'" + if (Test-Path -LiteralPath $collectionPath) { + $foundPath = $collectionPath + break + } + } + + if (-not $foundPath) { + throw "Failed to find collection path for $c" + } + + Write-Verbose "Using collection path '$foundPath' for $c" + + $metaPath = [Path]::Combine($foundPath, 'meta') + + $pwshUtilsPath = [Path]::Combine($foundPath, 'plugins', 'plugin_utils', 'powershell') + if (Test-Path -LiteralPath $pwshUtilsPath) { + Get-ChildItem -LiteralPath $pwshUtilsPath | ForEach-Object -Process { + if ($_.Extension -ne '.ps1') { + return + } + + $nameWithoutExt = "ansible_collections.$c.plugins.plugin_utils.powershell.$($File.BaseName)" + $nameWithExt = "$nameWithoutExt$($File.Extension)" + if ($nameWithoutExt -in $Skip -or $nameWithExt -in $Skip) { + Write-Verbose "Skipping script to sign '$nameWithExt' as it is in the supplied skip list" + return + } + + $pathsToSign.Add($_) + } + } + + $collectionPwshContent = [PSObject[]]@( + $utilPath = [Path]::Combine($foundPath, 'plugins', 'module_utils') + if (Test-Path -LiteralPath $utilPath) { + Get-ChildItem -LiteralPath $utilPath | Where-Object Extension -In '.cs', '.psm1' | + New-HashEntry -PluginBase "ansible_collections.$c.plugins.module_utils" -Skip $Skip + } + + $modulePath = [Path]::Combine($foundPath, 'plugins', 'modules') + if (Test-Path -LiteralPath $modulePath) { + Get-ChildItem -LiteralPath $modulePath | Where-Object Extension -EQ '.ps1' | + New-HashEntry -PluginBase $c -Skip $Skip + } + ) + $hashedPaths.AddRange($collectionPwshContent) + } + + $manifest = @( + '#AnsibleVersion 1' + '' + '@{' + ' HashList = @(' + foreach ($content in $hashedPaths) { + # To avoid encoding problems with Authenticode and non-ASCII + # characters, we escape them as Unicode code points. We also + # escape some ASCII control characters that can cause escaping + # problems like newlines. + $escapedName = [Regex]::Replace( + $content.Name, + '([^\u0020-\u007F])', + { '\u{0:x4}' -f ([uint16][char]$args[0].Value) }) + + $escapedHash = [CodeGeneration]::EscapeSingleQuotedStringContent($content.Hash) + $escapedMode = [CodeGeneration]::EscapeSingleQuotedStringContent($content.Mode) + " # $escapedName" + " @{" + " Hash = '$escapedHash'" + " Mode = '$escapedMode'" + " }" + } + ' )' + '}' + ) -join "`n" + $manifestPath = [Path]::Combine($metaPath, 'powershell_signatures.ps1') + Write-Verbose "Creating and signing manifest for $c at '$manifestPath'" + Set-Content -LiteralPath $manifestPath -Value $manifest -NoNewline + + Set-OpenAuthenticodeSignature -LiteralPath $manifestPath @signParams + + $pathsToSign | ForEach-Object -Process { + $tempPath = Join-Path $_.DirectoryName "$($_.BaseName)_tmp.ps1" + $_ | Copy-Item -Destination $tempPath -Force + + try { + Write-Verbose "Signing script '$($_.FullName)'" + Set-OpenAuthenticodeSignature -LiteralPath $tempPath @signParams + + $signedContent = Get-Content -LiteralPath $tempPath -Raw + $sigIndex = $signedContent.LastIndexOf("`r`n# SIG # Begin signature block`r`n") + if ($sigIndex -eq -1) { + throw "Failed to find signature block in $($_.FullName)" + } + + # Ignore the first and last \r\n when extracting the signature + $sigIndex += 2 + $signature = $signedContent.Substring($sigIndex, $signedContent.Length - $sigIndex - 2) + $sigPath = Join-Path $_.DirectoryName "$($_.Name).authenticode" + + Write-Verbose "Creating signature file at '$sigPath'" + Set-Content -LiteralPath $sigPath -Value $signature -NoNewline + } + finally { + $tempPath | Remove-Item -Force + } + } + } + catch { + $_.ErrorDetails = "Failed to process collection ${c}: $_" + $PSCmdlet.WriteError($_) + continue + } + } + } +} diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst new file mode 100644 index 00000000000..ac46e422f21 --- /dev/null +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -0,0 +1,141 @@ +.. _windows_wdac: + +Windows App Control +=================== +Windows App Control, formerly known as Windows Defender Application Control (``WDAC``), is a security feature of Windows that can be used to restrict what executables and scripts can be run on a Windows host. In the past, enabling WDAC will cause Ansible to fail when running on the Windows host. Starting with Ansible 2.19, Ansible can now run on Windows hosts with WDAC enabled. + +.. Warning:: + The App Control implementation is considered a tech preview and can change in future releases. It is not possible to ensure all PowerShell modules will work with App Control enabled and that a module might enable arbitrary code to run in a way not typically allowed by App Control. It is recommended to test all modules with WDAC enabled before using them in production. + +.. contents:: Topics + :local: + +Requirements for Ansible to work with App Control +------------------------------------------------- +Ansible requires the target Windows version to be Windows Server 2019 or Windows 10 Build 1803 or later. This is because the ``Dynamic Code Security`` feature added in that Windows version is required to allow Ansible to run tasks on the Windows host. + +The first step towards enabling App Control is to create a code signing certificate that will be used to sign the scripts used by Ansible. While this certificate can be self signed, it is recommended that it is issued by a trusted certificate authority used in your organisation. How to generate this certificate is outside the scope of this documentation. Once the certificate is the policy file must be generated and applied to the Windows host. + +Setting up App Control and configuring policies is not covered under the documentation here. Please read through the Microsoft documentation for `Application Control for Windows `_ or `Application Control with PowerShell `_ to understand how to configure App Control and set up policies. The `App Control for Business Wizard `_ is a good tool for generating WDAC policies through a more user friendly GUI. + +When setting up a policy it is recommended to configure Ansible through a supplemental policy so it can be easily modified and applied where Ansible will be used. While the Ansible configuration should be done in a supplemental policy, the base policy must have the following options set: + +* User Mode Code Integrity (``0 Enabled:UMCI``) is enabled +* Disable Script Enforcement (``11 Disabled:Script Enforcement``) is not enabled +* Dynamic Code Security (``19 Enabled:Dynamic Code Security``) is enabled + +The supplemental policy then should then add the certificate as a trusted publisher to the supplemental policy and apply that to the Windows host. This is an example policy configuration that contains a trusted publisher: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + +Once the policy is created and the certificate that will be used to sign the Ansible content is trusted, the policy can be applied to the Windows host. + +.. Warning:: + As Ansible typically runs tasks as an Adminstrator, it is important that the policy is signed and is applied so that Ansible cannot unset the policy through a task like ``win_file`` or ``win_regedit``. + +How to Sign Scripts +------------------- +Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The below PowerShell script can be used to sign both the Ansible internal execution scripts as well as any PowerShell collection content. It requires the following to run: + +* PowerShell 7.2 or later +* The `OpenAuthenticode `_ PowerShell module +* Python with Ansible and the required collections installed +* Access to the certificate private key trusted by the App Control policy + +.. literalinclude:: powershell/New-AnsiblePowerShellSignature.ps1 + :language: powershell + +To sign the Ansible content, and modules in a collection, the following PowerShell script can be used with the loaded function from above: + +.. code-block:: powershell + + $certPassword = Read-Host "Enter the password for the certificate" -AsSecureString + $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new( + "wdac-cert.pfx" + $certPassword) + + $collections = @( + # Includes all the builtin execution wrappers and scripts needed for Ansible + 'ansible.builtin' + + # Add any remaining collections used in the playbook like microsoft.ad, community.windows, etc. + 'ansible.windows' + ) + New-AnsiblePowerShellSignature -Certificate $cert -Verbose -Collection $collections + +The ``ansible.builtin`` collection refers to the builtin execution scripts used in Ansible. Any other collection used in the playbook should be added to the ``-Collection`` parameter. The script will generate the ``powershell_signatures.ps1`` script signed by the certificate and contains the hashes of all the modules in the collection that should be trusted to run. It will also generate the signature for Ansible's execution wrapper script in the Ansible installation directory so that Ansible can automatically run the script trusted by the App Control policy. The current behaviour of ``New-AnsiblePowerShellSignature`` is to sign all the modules in the collection and the Ansible execution wrapper script even if they could include an escape hatch. It is recommended to skip any modules using the ``-Skip`` parameter that are not needed in the playbook. + +.. code-block:: powershell + + New-AnsiblePowerShellSignature ... -Skip @( + 'ansible.windows.win_updates' + 'ansible.windows.win_dsc' + ) + +Any PowerShell content that is not part of a collection, like a custom script or code used in ``ansible.windows.win_powershell``, must be signed manually using the ``Set-AuthenticodeSignature`` cmdlet on Windows or ``Set-OpenAuthenticodeSignature`` using the ``OpenAuthenticode`` module on Linux. It is important that these signed scripts are used in a way that will not modify the contents of the script or else the signature will be invalidated. For example the ``ansible.builtin.script`` module will copy the script file to the target host as is an execute it leaving the signature intact. But using the ``ansible.builtin.file`` lookup will strip any remaining newline characters unless the ``rstrip=False`` option is used. + +.. note:: + The ``New-AnsiblePowerShellSignature`` function is a tech preview and may change in future releases. + +Known Module Differences +------------------------ +When App Control is enabled, some modules may not work as expected or at all even if signed. Some of the known differences are: + +* ``ansible.windows.win_command`` can only execute executables trusted by the App Control policy. If the executable is not trusted, the module will fail +* ``ansible.windows.win_shell`` will run in Constrained Language Mode (``CLM``) which is highly restricted and may cause some scripts to fail +* ``ansible.windows.win_powershell`` will run in CLM by default unless the provided script is signed +* ``ansible.builtin.script`` will run in CLM by default unless the provided script is signed +* ``ansible.windows.win_package`` can only run executables trusted by the App Control policy so may or may not work depending on the executable +* ``ansible.windows.win_updates`` is currently not supported and will not work + +Other modules that start sub-processes or rely on unsigned PowerShell content will most likely not work with App Control enabled. + +If trying to run a PowerShell script with ``ansible.windows.win_powershell`` or ``ansible.builtin.script``, the script itself must be signed or else it will be run in CLM. + +.. code-block:: yaml + + - name: Test out LanguageMode + ansible.windows.win_powershell: + script: $ExecutionContext.SessionState.LanguageMode + +Either the signed script can be placed include in the ``script`` option or the ``ansible.builtin.file`` lookup can be used to read the script from the filesystem. It is important to ensure that the ``file`` lookup is not going to strip any newline characters from the script to keep the signature intact. + +.. code-block:: yaml + + - name: Run signed script through script module + ansible.builtin.script: signed-script.ps1 + + - name: Run signed script through win_powershell module + ansible.windows.win_powershell: + script: "{{ lookup('ansible.builtin.file', 'signed-script.ps1', rstrip=False) }}" + + - name: Run signed script through win_powershell module with inlined script + ansible.windows.win_powershell: + script: | + $ExecutionContext.SessionState.LanguageMode + + # SIG # Begin signature block + # MIIFwAYJKoZIhvcNAQcCoIIFsTCCBa0CAQMxDTALBglghkgBZQMEAgEwewYKKwYB + ... + # SIG # End signature block + +.. note:: + Using the ``win_powershell`` method will read the script file as a UTF-8 encoded script. This may cause signature validation issues if the script is not UTF-8 encoded when signed or was signed with UTF-8 + BOM. From 54cf15cbfa47ca3b762cbd047f1043d09b82e12b Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 7 Feb 2025 06:38:45 +1000 Subject: [PATCH 02/10] Update script for better Python detection --- .../New-AnsiblePowerShellSignature.ps1 | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 b/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 index 7c908f57340..31ff3ecb2e4 100644 --- a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 +++ b/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 @@ -123,6 +123,11 @@ Function New-AnsiblePowerShellSignature { begin { Write-Verbose "Attempting to get ansible-config dump" + + $env:ANSIBLE_VERBOSITY = "0" + $env:ANSIBLE_DEVEL_WARNING = "false" + $env:ANSIBLE_INVENTORY_UNPARSED_WARNING = "false" + $env:ANSIBLE_NOCOLOR = "true" $configRaw = ansible-config dump --format json --type base 2>&1 if ($LASTEXITCODE) { $err = [ErrorRecord]::new( @@ -208,8 +213,17 @@ Function New-AnsiblePowerShellSignature { $hashedPaths = [List[PSObject]]::new() if ($c -eq 'ansible.builtin') { + Write-Verbose "Attempting to get Ansible python path" Write-Verbose "Attempting to get Ansible installation path" - $ansiblePath = python -c "import ansible; print(ansible.__file__)" 2>&1 + + $ansiblePython = ansible localhost -m debug -a 'var=ansible_playbook_python' + $resultStart = ($ansiblePython -join "").IndexOf('{') + if ($LASTEXITCODE -or $resultStart -eq -1) { + throw "Failed to find Ansible Python Interpreter path, RC: ${LASTEXITCODE} - $ansiblePython" + } + $python = (($ansiblePython -join "").Substring($resultStart) | ConvertFrom-Json).ansible_playbook_python + + $ansiblePath = & $python -c "import ansible; print(ansible.__file__)" 2>&1 if ($LASTEXITCODE) { throw "Failed to find Ansible installation path, RC: ${LASTEXITCODE} - $ansiblePath" } From 8ac0f7c6fe879fa501582387a95795391116556e Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 23 May 2025 05:33:22 +1000 Subject: [PATCH 03/10] Another passthrough and update script --- docs/docsite/rst/os_guide/intro_windows.rst | 2 + .../New-AnsiblePowerShellSignature.ps1 | 120 +++++++++++------- .../rst/os_guide/windows_app_control.rst | 102 ++++++++------- 3 files changed, 132 insertions(+), 92 deletions(-) diff --git a/docs/docsite/rst/os_guide/intro_windows.rst b/docs/docsite/rst/os_guide/intro_windows.rst index cf705a6588d..56b7bb979e4 100644 --- a/docs/docsite/rst/os_guide/intro_windows.rst +++ b/docs/docsite/rst/os_guide/intro_windows.rst @@ -162,6 +162,8 @@ While not all connection plugins require the connection user to be a member of t Learning Ansible's configuration management language :ref:`developing_modules` How to write modules + :ref:`windows_app_control` + Using Ansible with Windows App Control managed hosts :ref:`windows_dsc` Using Ansible with Windows Desired State Configuration :ref:`windows_performance` diff --git a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 b/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 index 31ff3ecb2e4..24ecebc0a46 100644 --- a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 +++ b/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 @@ -1,4 +1,6 @@ -#Requires -Module OpenAuthenticode +# 0.5.0 fixed BOM-less encoding issues with Unicode +#Requires -Modules @{ ModuleName = 'OpenAuthenticode'; ModuleVersion = '0.5.0' } +#Requires -Version 7.4 using namespace System.Collections.Generic using namespace System.IO @@ -9,14 +11,14 @@ using namespace System.Security.Cryptography.X509Certificates Function New-AnsiblePowerShellSignature { <# .SYNOPSIS - Creates and signed Ansible content for WDAC. + Creates and signed Ansible content for App Control/WDAC. .DESCRIPTION - This function will generate the powershell_signatures.ps1 manifest and sign + This function will generate the powershell_signatures.psd1 manifest and sign it. The manifest file includes all PowerShell/C# module_utils and PowerShell modules in the collection(s) specified. It will also create the - '*.authenticode' signature files for any PowerShell scripts found in the - collections plugins/plugin_utils/powershell directories. + '*.authenticode' signature file for the exec_wrapper.ps1 used inside + Ansible itself. .PARAMETER Certificate The certificate to use for signing the content. @@ -27,9 +29,8 @@ Function New-AnsiblePowerShellSignature { .PARAMETER Skip A list of plugins to skip by the fully qualified name. Plugins skipped will - be marked as unsupported in the manifest and will error when being run. - Scripts skipped are just ignored and will not have its '.authenticode' - signature file created. + not be included in the signed manifest. This means that modules will be run + in CLM mode and module_utils will be skipped entirely. The values in the list should be the fully qualified name of the plugin as referenced in Ansible. The value can also optionally include the extension @@ -55,8 +56,10 @@ Function New-AnsiblePowerShellSignature { 'ansible_collections.namespace.name.plugins.module_utils.CSharpUtil' 'ansible_collections.namespace.name.plugins.module_utils.CSharpUtil.cs' - # Collection Scripts - 'ansible_collections.namespace.name.plugins.plugin_utils.powershell.script' + .PARAMETER Unsupported + A list of plugins to be marked as unsupported in the manifest and will + error when being run. Like -Skip, the values here are the fully qualified + name of the plugin as referenced in Ansible. .PARAMETER TimeStampServer Optional authenticode timestamp server to use when signing the content. @@ -94,6 +97,10 @@ Function New-AnsiblePowerShellSignature { ) $cert = [X509Certificate2]::new("wdac-cert.pfx", "password") New-AnsiblePowerShellSignature -Certificate $cert -Collection namespace.name -Skip $skip + + .NOTES + This function requires Ansible to be installed and available in the PATH so + it can find the Ansible installation and collection paths. #> [CmdletBinding()] param ( @@ -116,14 +123,34 @@ Function New-AnsiblePowerShellSignature { [string[]] $Skip = @(), + [Parameter( + ValueFromPipelineByPropertyName + )] + [string[]] + $Unsupported = @(), + [Parameter()] [string] $TimeStampServer ) begin { - Write-Verbose "Attempting to get ansible-config dump" + $backupEnv = @{ + ANSIBLE_VERBOSITY = $env:ANSIBLE_VERBOSITY + ANSIBLE_DEVEL_WARNING = $env:ANSIBLE_DEVEL_WARNING + ANSIBLE_INVENTORY_UNPARSED_WARNING = $env:ANSIBLE_INVENTORY_UNPARSED_WARNING + ANSIBLE_NOCOLOR = $env:ANSIBLE_NOCOLOR + } + + $Unsupported = @( + $Unsupported + # Known to not work, requires more changes to both Ansible and + # win_updates to support so we hardcode this as unsupported. + 'ansible.windows.win_updates' + ) + + Write-Verbose "Attempting to get ansible-config dump" $env:ANSIBLE_VERBOSITY = "0" $env:ANSIBLE_DEVEL_WARNING = "false" $env:ANSIBLE_INVENTORY_UNPARSED_WARNING = "false" @@ -142,14 +169,6 @@ Function New-AnsiblePowerShellSignature { $collectionsPaths = @($config | Where-Object name -EQ 'COLLECTIONS_PATHS' | ForEach-Object value) Write-Verbose "Collections paths to be searched: [$($collectionsPaths -join ":")]" - # FIXME: Don't hardcode this list - $Skip = @( - $Skip - - # Known to not work, requires more changes - 'ansible.windows.win_updates' - ) - $signParams = @{ Certificate = $Certificate HashAlgorithm = 'SHA256' @@ -173,6 +192,11 @@ Function New-AnsiblePowerShellSignature { [string] $PluginBase, + [Parameter()] + [AllowEmptyCollection()] + [string[]] + $Unsupported = @(), + [Parameter()] [AllowEmptyCollection()] [string[]] @@ -185,7 +209,11 @@ Function New-AnsiblePowerShellSignature { $mode = 'Trusted' if ($nameWithoutExt -in $Skip -or $nameWithExt -in $Skip) { - Write-Verbose "Marking plugin '$nameWithExt' as unsupported as it is in the supplied skip list" + Write-Verbose "Skipping plugin '$nameWithExt' as it is in the supplied skip list" + return + } + elseif ($nameWithoutExt -in $Unsupported -or $nameWithExt -in $Unsupported) { + Write-Verbose "Marking plugin '$nameWithExt' as unsupported as it is in the unsupported list" $mode = 'Unsupported' } @@ -201,6 +229,11 @@ Function New-AnsiblePowerShellSignature { } process { + $newHashParams = @{ + Skip = $Skip + Unsupported = $Unsupported + } + foreach ($c in $Collection) { try { if (-not $checked.Add($c)) { @@ -214,8 +247,6 @@ Function New-AnsiblePowerShellSignature { if ($c -eq 'ansible.builtin') { Write-Verbose "Attempting to get Ansible python path" - Write-Verbose "Attempting to get Ansible installation path" - $ansiblePython = ansible localhost -m debug -a 'var=ansible_playbook_python' $resultStart = ($ansiblePython -join "").IndexOf('{') if ($LASTEXITCODE -or $resultStart -eq -1) { @@ -223,6 +254,7 @@ Function New-AnsiblePowerShellSignature { } $python = (($ansiblePython -join "").Substring($resultStart) | ConvertFrom-Json).ansible_playbook_python + Write-Verbose "Attempting to get Ansible installation path" $ansiblePath = & $python -c "import ansible; print(ansible.__file__)" 2>&1 if ($LASTEXITCODE) { throw "Failed to find Ansible installation path, RC: ${LASTEXITCODE} - $ansiblePath" @@ -241,12 +273,12 @@ Function New-AnsiblePowerShellSignature { # Builtin utils are special where the filename is their FQN Get-ChildItem -Path ([Path]::Combine($ansibleBase, 'module_utils', 'csharp', '*.cs')) | - New-HashEntry -PluginBase "" -Skip $Skip + New-HashEntry -PluginBase "" @newHashParams Get-ChildItem -Path ([Path]::Combine($ansibleBase, 'module_utils', 'powershell', '*.psm1')) | - New-HashEntry -PluginBase "" -Skip $Skip + New-HashEntry -PluginBase "" @newHashParams Get-ChildItem -Path ([Path]::Combine($ansibleBase, 'modules', '*.ps1')) | - New-HashEntry -PluginBase $c -Skip $Skip + New-HashEntry -PluginBase $c @newHashParams ) $hashedPaths.AddRange($ansiblePwshContent) } @@ -276,44 +308,30 @@ Function New-AnsiblePowerShellSignature { $metaPath = [Path]::Combine($foundPath, 'meta') - $pwshUtilsPath = [Path]::Combine($foundPath, 'plugins', 'plugin_utils', 'powershell') - if (Test-Path -LiteralPath $pwshUtilsPath) { - Get-ChildItem -LiteralPath $pwshUtilsPath | ForEach-Object -Process { - if ($_.Extension -ne '.ps1') { - return - } - - $nameWithoutExt = "ansible_collections.$c.plugins.plugin_utils.powershell.$($File.BaseName)" - $nameWithExt = "$nameWithoutExt$($File.Extension)" - if ($nameWithoutExt -in $Skip -or $nameWithExt -in $Skip) { - Write-Verbose "Skipping script to sign '$nameWithExt' as it is in the supplied skip list" - return - } - - $pathsToSign.Add($_) - } - } - $collectionPwshContent = [PSObject[]]@( $utilPath = [Path]::Combine($foundPath, 'plugins', 'module_utils') if (Test-Path -LiteralPath $utilPath) { Get-ChildItem -LiteralPath $utilPath | Where-Object Extension -In '.cs', '.psm1' | - New-HashEntry -PluginBase "ansible_collections.$c.plugins.module_utils" -Skip $Skip + New-HashEntry -PluginBase "ansible_collections.$c.plugins.module_utils" @newHashParams } $modulePath = [Path]::Combine($foundPath, 'plugins', 'modules') if (Test-Path -LiteralPath $modulePath) { Get-ChildItem -LiteralPath $modulePath | Where-Object Extension -EQ '.ps1' | - New-HashEntry -PluginBase $c -Skip $Skip + New-HashEntry -PluginBase $c @newHashParams } ) $hashedPaths.AddRange($collectionPwshContent) } + if (-not (Test-Path -LiteralPath $metaPath)) { + Write-Verbose "Creating meta path '$metaPath'" + New-Item -Path $metaPath -ItemType Directory -Force | Out-Null + } + $manifest = @( - '#AnsibleVersion 1' - '' '@{' + ' Version = 1' ' HashList = @(' foreach ($content in $hashedPaths) { # To avoid encoding problems with Authenticode and non-ASCII @@ -336,7 +354,7 @@ Function New-AnsiblePowerShellSignature { ' )' '}' ) -join "`n" - $manifestPath = [Path]::Combine($metaPath, 'powershell_signatures.ps1') + $manifestPath = [Path]::Combine($metaPath, 'powershell_signatures.psd1') Write-Verbose "Creating and signing manifest for $c at '$manifestPath'" Set-Content -LiteralPath $manifestPath -Value $manifest -NoNewline @@ -376,4 +394,10 @@ Function New-AnsiblePowerShellSignature { } } } + + clean { + foreach ($e in $backupEnv.GetEnumerator()) { + Set-Item -Path "env:$($e.Key)" -Value $e.Value + } + } } diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst index ac46e422f21..6e316681a99 100644 --- a/docs/docsite/rst/os_guide/windows_app_control.rst +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -1,8 +1,8 @@ -.. _windows_wdac: +.. _windows_app_control: Windows App Control =================== -Windows App Control, formerly known as Windows Defender Application Control (``WDAC``), is a security feature of Windows that can be used to restrict what executables and scripts can be run on a Windows host. In the past, enabling WDAC will cause Ansible to fail when running on the Windows host. Starting with Ansible 2.19, Ansible can now run on Windows hosts with WDAC enabled. +Windows App Control, formerly known as Windows Defender Application Control (``WDAC``), is a security feature of Windows that can be used to restrict what executables and scripts can be run on a Windows host. In the past, enabling WDAC will cause Ansible to fail when running on the Windows host. Starting with Ansible 2.19 and the ``ansible.windows`` collection at ``3.1.0``, Ansible can now run on Windows hosts with WDAC enabled. .. Warning:: The App Control implementation is considered a tech preview and can change in future releases. It is not possible to ensure all PowerShell modules will work with App Control enabled and that a module might enable arbitrary code to run in a way not typically allowed by App Control. It is recommended to test all modules with WDAC enabled before using them in production. @@ -14,20 +14,23 @@ Requirements for Ansible to work with App Control ------------------------------------------------- Ansible requires the target Windows version to be Windows Server 2019 or Windows 10 Build 1803 or later. This is because the ``Dynamic Code Security`` feature added in that Windows version is required to allow Ansible to run tasks on the Windows host. -The first step towards enabling App Control is to create a code signing certificate that will be used to sign the scripts used by Ansible. While this certificate can be self signed, it is recommended that it is issued by a trusted certificate authority used in your organisation. How to generate this certificate is outside the scope of this documentation. Once the certificate is the policy file must be generated and applied to the Windows host. +The first step towards enabling App Control is to create a code signing certificate that will be used to sign the scripts used by Ansible. While this certificate can be self signed, it is recommended that it is issued by a trusted certificate authority used in your organisation. How to generate this certificate is outside the scope of this documentation. Once the certificate is setup, the policy file must be generated and applied to the Windows host. -Setting up App Control and configuring policies is not covered under the documentation here. Please read through the Microsoft documentation for `Application Control for Windows `_ or `Application Control with PowerShell `_ to understand how to configure App Control and set up policies. The `App Control for Business Wizard `_ is a good tool for generating WDAC policies through a more user friendly GUI. +Setting up App Control and configuring policies is not covered under the documentation here. Please read through the Microsoft documentation for `Application Control for Windows `_ or `Application Control with PowerShell `_ to understand how to configure App Control and set up policies. The `App Control for Business Wizard `_ is a tool that can simplify policy generation through a more user friendly GUI. -When setting up a policy it is recommended to configure Ansible through a supplemental policy so it can be easily modified and applied where Ansible will be used. While the Ansible configuration should be done in a supplemental policy, the base policy must have the following options set: +When setting up a policy it is recommended to configure Ansible as a supplemental policy so it can be easily modified and applied where Ansible will be used. Whether you use a supplemental or just a base policy for trusting the certificate used by Ansible, the base policy must have the following options set: * User Mode Code Integrity (``0 Enabled:UMCI``) is enabled * Disable Script Enforcement (``11 Disabled:Script Enforcement``) is not enabled * Dynamic Code Security (``19 Enabled:Dynamic Code Security``) is enabled -The supplemental policy then should then add the certificate as a trusted publisher to the supplemental policy and apply that to the Windows host. This is an example policy configuration that contains a trusted publisher: +The policy then should then add the certificate as a trusted publisher to the ``User Mode Signing Scenario``, for example this is an example policy configuration that contains a trusted publisher: .. code-block:: xml - + + + ... + @@ -45,25 +48,30 @@ The supplemental policy then should then add the certificate as a trusted publis + ... + -Once the policy is created and the certificate that will be used to sign the Ansible content is trusted, the policy can be applied to the Windows host. +Once the policy is created and the certificate that will be used to sign the Ansible content is trusted by the Windows host, the policy can be applied. .. Warning:: As Ansible typically runs tasks as an Adminstrator, it is important that the policy is signed and is applied so that Ansible cannot unset the policy through a task like ``win_file`` or ``win_regedit``. -How to Sign Scripts -------------------- -Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The below PowerShell script can be used to sign both the Ansible internal execution scripts as well as any PowerShell collection content. It requires the following to run: +How to Sign Ansible Content +--------------------------- +Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The below PowerShell script can be used to sign both the execution wrappers used by Ansible to invoke modules but also any PowerShell modules inside an Ansible collection. It requires the following to run: -* PowerShell 7.2 or later +* PowerShell 7.4 or later * The `OpenAuthenticode `_ PowerShell module * Python with Ansible and the required collections installed -* Access to the certificate private key trusted by the App Control policy +* Access to the certificate and private key trusted by the App Control policy, typically as a PFX file .. literalinclude:: powershell/New-AnsiblePowerShellSignature.ps1 :language: powershell -To sign the Ansible content, and modules in a collection, the following PowerShell script can be used with the loaded function from above: +.. note:: + The ``New-AnsiblePowerShellSignature`` function is not officially supported and is marked as a tech preview. + +To sign the Ansible PowerShell wrapper scripts, and modules in a collection, the following PowerShell script can be used with the loaded function from above: .. code-block:: powershell @@ -72,35 +80,44 @@ To sign the Ansible content, and modules in a collection, the following PowerShe "wdac-cert.pfx" $certPassword) - $collections = @( - # Includes all the builtin execution wrappers and scripts needed for Ansible - 'ansible.builtin' + $signingParams = @{ + Certificate = $cert - # Add any remaining collections used in the playbook like microsoft.ad, community.windows, etc. - 'ansible.windows' - ) - New-AnsiblePowerShellSignature -Certificate $cert -Verbose -Collection $collections + Collection = @( + # Includes all the builtin execution wrappers and scripts needed for Ansible + 'ansible.builtin' -The ``ansible.builtin`` collection refers to the builtin execution scripts used in Ansible. Any other collection used in the playbook should be added to the ``-Collection`` parameter. The script will generate the ``powershell_signatures.ps1`` script signed by the certificate and contains the hashes of all the modules in the collection that should be trusted to run. It will also generate the signature for Ansible's execution wrapper script in the Ansible installation directory so that Ansible can automatically run the script trusted by the App Control policy. The current behaviour of ``New-AnsiblePowerShellSignature`` is to sign all the modules in the collection and the Ansible execution wrapper script even if they could include an escape hatch. It is recommended to skip any modules using the ``-Skip`` parameter that are not needed in the playbook. + # Add any remaining collections used in the playbook like microsoft.ad, community.windows, etc. + 'ansible.windows' + 'microsoft.ad' + 'microsoft.iis' + 'community.windows' + ) + + # The URL of the Authenticode timestamp server to use for timestamping + # the signature. + # https://learn.microsoft.com/en-us/windows/win32/seccrypto/time-stamping-authenticode-signatures + TimeStampServer = '...' + } + New-AnsiblePowerShellSignature @signingParams -Verbose + +The ``ansible.builtin`` collection refers to the builtin execution scripts used in Ansible. Any other collection with PowerShell modules used in the playbook should be added to the ``-Collection`` parameter. The script will generate the ``powershell_signatures.psd1`` script signed by the certificate and contains the hashes of all the modules in the collection that should be trusted to run. It will also generate the signature for Ansible's execution wrapper script in the Ansible installation directory so that Ansible can automatically run the script trusted by the App Control policy. The current behaviour of ``New-AnsiblePowerShellSignature`` is to sign all the modules in the collection and the Ansible execution wrapper script even if they could include an escape hatch. It is recommended to skip any modules using the ``-Skip`` parameter that are not needed in the playbook, for example: .. code-block:: powershell New-AnsiblePowerShellSignature ... -Skip @( - 'ansible.windows.win_updates' 'ansible.windows.win_dsc' + 'ansible.windows.win_timezone' ) -Any PowerShell content that is not part of a collection, like a custom script or code used in ``ansible.windows.win_powershell``, must be signed manually using the ``Set-AuthenticodeSignature`` cmdlet on Windows or ``Set-OpenAuthenticodeSignature`` using the ``OpenAuthenticode`` module on Linux. It is important that these signed scripts are used in a way that will not modify the contents of the script or else the signature will be invalidated. For example the ``ansible.builtin.script`` module will copy the script file to the target host as is an execute it leaving the signature intact. But using the ``ansible.builtin.file`` lookup will strip any remaining newline characters unless the ``rstrip=False`` option is used. - -.. note:: - The ``New-AnsiblePowerShellSignature`` function is a tech preview and may change in future releases. +Any PowerShell content that is not part of a collection, like custom scripts or code used in ``ansible.windows.win_powershell``, must be signed manually using the ``Set-AuthenticodeSignature`` cmdlet on Windows or ``Set-OpenAuthenticodeSignature`` through ``OpenAuthenticode`` module on Linux. It is important that these signed scripts are used in a way that will not modify the contents of the script or else the signature will be invalidated. For example the ``ansible.builtin.script`` module will copy the script file to the target host as is leaving the signature intact but using the ``ansible.builtin.file`` lookup will strip any remaining newline characters unless the ``rstrip=False`` option is used. Known Module Differences ------------------------ -When App Control is enabled, some modules may not work as expected or at all even if signed. Some of the known differences are: +When App Control is enabled, some modules may not work, or behave differently, even if signed. Some of the known differences are: * ``ansible.windows.win_command`` can only execute executables trusted by the App Control policy. If the executable is not trusted, the module will fail -* ``ansible.windows.win_shell`` will run in Constrained Language Mode (``CLM``) which is highly restricted and may cause some scripts to fail +* ``ansible.windows.win_shell`` will run all code in Constrained Language Mode (``CLM``) which is highly restricted and may cause some scripts to fail * ``ansible.windows.win_powershell`` will run in CLM by default unless the provided script is signed * ``ansible.builtin.script`` will run in CLM by default unless the provided script is signed * ``ansible.windows.win_package`` can only run executables trusted by the App Control policy so may or may not work depending on the executable @@ -116,26 +133,23 @@ If trying to run a PowerShell script with ``ansible.windows.win_powershell`` or ansible.windows.win_powershell: script: $ExecutionContext.SessionState.LanguageMode -Either the signed script can be placed include in the ``script`` option or the ``ansible.builtin.file`` lookup can be used to read the script from the filesystem. It is important to ensure that the ``file`` lookup is not going to strip any newline characters from the script to keep the signature intact. +It is important that when referencing a signed script that the script is not modified in any way. This means the line endings and whitespace that were present when it was signed must be the same when Ansible uses the signed script. + +.. note:: + Ansible will always load the script with the UTF-8 encoding even if no Byte Order Mark (``BOM``) is present. It is important that the script was encoded with UTF-8 without a BOM when it was signed so that the signature stays valid. If the script was signed with a different encoding, the signature could be invalidated or PowerShell may interpret it with different characters. + +When referencing a signed script in Ansible, it is important that it is used in a way that does not modify the contents of the script which would break the signature. For example you should have the signed script in the local ``files`` directory associated with the playbook/tasks and reference in one of the following ways: .. code-block:: yaml - - name: Run signed script through script module + - name: Run signed script through the script module ansible.builtin.script: signed-script.ps1 - - name: Run signed script through win_powershell module + - name: Run signed script through win_powershell as a path ansible.windows.win_powershell: - script: "{{ lookup('ansible.builtin.file', 'signed-script.ps1', rstrip=False) }}" + path: signed-script.ps1 - - name: Run signed script through win_powershell module with inlined script + - name: Run signed script through win_powershell as inline content ansible.windows.win_powershell: - script: | - $ExecutionContext.SessionState.LanguageMode - - # SIG # Begin signature block - # MIIFwAYJKoZIhvcNAQcCoIIFsTCCBa0CAQMxDTALBglghkgBZQMEAgEwewYKKwYB - ... - # SIG # End signature block - -.. note:: - Using the ``win_powershell`` method will read the script file as a UTF-8 encoded script. This may cause signature validation issues if the script is not UTF-8 encoded when signed or was signed with UTF-8 + BOM. + # rstrip=False is important so the last \r\n of the signature is not removed. + script: "{{ lookup('ansible.builtin.file', 'signed-script.ps1', rstrip=False) }}" From 4c7d9d9f227f1800dcd88acb9d7781452ae2a1ea Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 23 May 2025 05:38:48 +1000 Subject: [PATCH 04/10] Linting fixes --- .../powershell/New-AnsiblePowerShellSignature.ps1 | 4 ++-- docs/docsite/rst/os_guide/windows_app_control.rst | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 b/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 index 24ecebc0a46..e6d73c1e228 100644 --- a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 +++ b/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 @@ -25,7 +25,7 @@ Function New-AnsiblePowerShellSignature { .PARAMETER Collection The collection(s) to sign. This is set to ansible.builtin by default but - can be overriden to include other collections like ansible.windows. + can be overridden to include other collections like ansible.windows. .PARAMETER Skip A list of plugins to skip by the fully qualified name. Plugins skipped will @@ -34,7 +34,7 @@ Function New-AnsiblePowerShellSignature { The values in the list should be the fully qualified name of the plugin as referenced in Ansible. The value can also optionally include the extension - of the file if the FQN is ambigious, e.g. collection util that has both a + of the file if the FQN is ambiguous, e.g. collection util that has both a PowerShell and C# util of the same name. Here are some examples for the various content types: diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst index 6e316681a99..2a156fef264 100644 --- a/docs/docsite/rst/os_guide/windows_app_control.rst +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -14,7 +14,7 @@ Requirements for Ansible to work with App Control ------------------------------------------------- Ansible requires the target Windows version to be Windows Server 2019 or Windows 10 Build 1803 or later. This is because the ``Dynamic Code Security`` feature added in that Windows version is required to allow Ansible to run tasks on the Windows host. -The first step towards enabling App Control is to create a code signing certificate that will be used to sign the scripts used by Ansible. While this certificate can be self signed, it is recommended that it is issued by a trusted certificate authority used in your organisation. How to generate this certificate is outside the scope of this documentation. Once the certificate is setup, the policy file must be generated and applied to the Windows host. +The first step towards enabling App Control is to create a code signing certificate that will be used to sign the scripts used by Ansible. While this certificate can be self signed, it is recommended that it is issued by a trusted certificate authority used in your organization. How to generate this certificate is outside the scope of this documentation. Once the certificate is setup, the policy file must be generated and applied to the Windows host. Setting up App Control and configuring policies is not covered under the documentation here. Please read through the Microsoft documentation for `Application Control for Windows `_ or `Application Control with PowerShell `_ to understand how to configure App Control and set up policies. The `App Control for Business Wizard `_ is a tool that can simplify policy generation through a more user friendly GUI. @@ -26,7 +26,7 @@ When setting up a policy it is recommended to configure Ansible as a supplementa The policy then should then add the certificate as a trusted publisher to the ``User Mode Signing Scenario``, for example this is an example policy configuration that contains a trusted publisher: -.. code-block:: xml +.. code-block:: text ... @@ -54,7 +54,7 @@ The policy then should then add the certificate as a trusted publisher to the `` Once the policy is created and the certificate that will be used to sign the Ansible content is trusted by the Windows host, the policy can be applied. .. Warning:: - As Ansible typically runs tasks as an Adminstrator, it is important that the policy is signed and is applied so that Ansible cannot unset the policy through a task like ``win_file`` or ``win_regedit``. + As Ansible typically runs tasks as an Administrator, it is important that the policy is signed and is applied so that Ansible cannot unset the policy through a task like ``win_file`` or ``win_regedit``. How to Sign Ansible Content --------------------------- @@ -101,7 +101,7 @@ To sign the Ansible PowerShell wrapper scripts, and modules in a collection, the } New-AnsiblePowerShellSignature @signingParams -Verbose -The ``ansible.builtin`` collection refers to the builtin execution scripts used in Ansible. Any other collection with PowerShell modules used in the playbook should be added to the ``-Collection`` parameter. The script will generate the ``powershell_signatures.psd1`` script signed by the certificate and contains the hashes of all the modules in the collection that should be trusted to run. It will also generate the signature for Ansible's execution wrapper script in the Ansible installation directory so that Ansible can automatically run the script trusted by the App Control policy. The current behaviour of ``New-AnsiblePowerShellSignature`` is to sign all the modules in the collection and the Ansible execution wrapper script even if they could include an escape hatch. It is recommended to skip any modules using the ``-Skip`` parameter that are not needed in the playbook, for example: +The ``ansible.builtin`` collection refers to the builtin execution scripts used in Ansible. Any other collection with PowerShell modules used in the playbook should be added to the ``-Collection`` parameter. The script will generate the ``powershell_signatures.psd1`` script signed by the certificate and contains the hashes of all the modules in the collection that should be trusted to run. It will also generate the signature for Ansible's execution wrapper script in the Ansible installation directory so that Ansible can automatically run the script trusted by the App Control policy. The current behavior of ``New-AnsiblePowerShellSignature`` is to sign all the modules in the collection and the Ansible execution wrapper script even if they could include an escape hatch. It is recommended to skip any modules using the ``-Skip`` parameter that are not needed in the playbook, for example: .. code-block:: powershell From 5f5f9f6452baa61a5040333fbb0fb3e70468f65f Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Mon, 30 Jun 2025 11:57:41 +1000 Subject: [PATCH 05/10] Update docs/docsite/rst/os_guide/windows_app_control.rst Co-authored-by: Abhijeet Kasurde --- docs/docsite/rst/os_guide/windows_app_control.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst index 2a156fef264..9be2dc261e9 100644 --- a/docs/docsite/rst/os_guide/windows_app_control.rst +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -2,7 +2,7 @@ Windows App Control =================== -Windows App Control, formerly known as Windows Defender Application Control (``WDAC``), is a security feature of Windows that can be used to restrict what executables and scripts can be run on a Windows host. In the past, enabling WDAC will cause Ansible to fail when running on the Windows host. Starting with Ansible 2.19 and the ``ansible.windows`` collection at ``3.1.0``, Ansible can now run on Windows hosts with WDAC enabled. +`Windows App Control `_, formerly known as Windows Defender Application Control (``WDAC``), is a security feature of Windows that can be used to restrict what executables and scripts can be run on a Windows host. In the past, enabling WDAC will cause Ansible to fail when running on the Windows host. Starting with Ansible 2.19 and the ``ansible.windows`` collection at ``3.1.0``, Ansible can now run on Windows hosts with WDAC enabled. .. Warning:: The App Control implementation is considered a tech preview and can change in future releases. It is not possible to ensure all PowerShell modules will work with App Control enabled and that a module might enable arbitrary code to run in a way not typically allowed by App Control. It is recommended to test all modules with WDAC enabled before using them in production. From 55fc112a6b9727af4c5559df4489e632fd5e78f5 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Mon, 30 Jun 2025 13:13:47 +1000 Subject: [PATCH 06/10] Remove inlined script in favour of hyperlink --- docs/docsite/rst/os_guide/windows_app_control.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst index 9be2dc261e9..dc81eee4102 100644 --- a/docs/docsite/rst/os_guide/windows_app_control.rst +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -58,16 +58,13 @@ Once the policy is created and the certificate that will be used to sign the Ans How to Sign Ansible Content --------------------------- -Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The below PowerShell script can be used to sign both the execution wrappers used by Ansible to invoke modules but also any PowerShell modules inside an Ansible collection. It requires the following to run: +Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The PowerShell script `New-AnsiblePowerShellSignature.ps1 ` can be used to sign both the execution wrapper used by Ansible to invoke modules and any PowerShell modules inside an Ansible collection. It requires the following to run: * PowerShell 7.4 or later * The `OpenAuthenticode `_ PowerShell module * Python with Ansible and the required collections installed * Access to the certificate and private key trusted by the App Control policy, typically as a PFX file -.. literalinclude:: powershell/New-AnsiblePowerShellSignature.ps1 - :language: powershell - .. note:: The ``New-AnsiblePowerShellSignature`` function is not officially supported and is marked as a tech preview. From 1cf0acd163b2ae16101579b639a20be68415f67f Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 11 Jul 2025 06:59:55 +1000 Subject: [PATCH 07/10] Update docs/docsite/rst/os_guide/windows_app_control.rst Co-authored-by: Sandra McCann --- docs/docsite/rst/os_guide/windows_app_control.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst index dc81eee4102..3b0f6525f6e 100644 --- a/docs/docsite/rst/os_guide/windows_app_control.rst +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -7,7 +7,7 @@ Windows App Control .. Warning:: The App Control implementation is considered a tech preview and can change in future releases. It is not possible to ensure all PowerShell modules will work with App Control enabled and that a module might enable arbitrary code to run in a way not typically allowed by App Control. It is recommended to test all modules with WDAC enabled before using them in production. -.. contents:: Topics +.. contents:: :local: Requirements for Ansible to work with App Control From 7bf020a82ea3fc15fe06405efe470fa3e5ede4b3 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 15 Jul 2025 09:51:48 +1000 Subject: [PATCH 08/10] Fix up from review comments --- docs/docsite/rst/os_guide/windows_app_control.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst index 3b0f6525f6e..8384979c047 100644 --- a/docs/docsite/rst/os_guide/windows_app_control.rst +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -4,8 +4,8 @@ Windows App Control =================== `Windows App Control `_, formerly known as Windows Defender Application Control (``WDAC``), is a security feature of Windows that can be used to restrict what executables and scripts can be run on a Windows host. In the past, enabling WDAC will cause Ansible to fail when running on the Windows host. Starting with Ansible 2.19 and the ``ansible.windows`` collection at ``3.1.0``, Ansible can now run on Windows hosts with WDAC enabled. -.. Warning:: - The App Control implementation is considered a tech preview and can change in future releases. It is not possible to ensure all PowerShell modules will work with App Control enabled and that a module might enable arbitrary code to run in a way not typically allowed by App Control. It is recommended to test all modules with WDAC enabled before using them in production. +.. admonition:: Experimental functionality + The App Control implementation is considered an experimental feature and can change in future releases. It is not possible to ensure all PowerShell modules will work with App Control enabled and that a module might enable arbitrary code to run in a way not typically allowed by App Control. It is recommended to test all modules with WDAC enabled before using them in production. .. contents:: :local: @@ -58,7 +58,7 @@ Once the policy is created and the certificate that will be used to sign the Ans How to Sign Ansible Content --------------------------- -Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The PowerShell script `New-AnsiblePowerShellSignature.ps1 ` can be used to sign both the execution wrapper used by Ansible to invoke modules and any PowerShell modules inside an Ansible collection. It requires the following to run: +Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The PowerShell script `New-AnsiblePowerShellSignature.ps1 `_ can be used to sign both the execution wrapper used by Ansible to invoke modules and any PowerShell modules inside an Ansible collection. It requires the following to run: * PowerShell 7.4 or later * The `OpenAuthenticode `_ PowerShell module From 3e8803b7a20c883307d3c390206aeb1d246f1a4b Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 15 Jul 2025 09:54:53 +1000 Subject: [PATCH 09/10] Try and fix admonition content block --- docs/docsite/rst/os_guide/windows_app_control.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst index 8384979c047..a7f81c60820 100644 --- a/docs/docsite/rst/os_guide/windows_app_control.rst +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -5,7 +5,8 @@ Windows App Control `Windows App Control `_, formerly known as Windows Defender Application Control (``WDAC``), is a security feature of Windows that can be used to restrict what executables and scripts can be run on a Windows host. In the past, enabling WDAC will cause Ansible to fail when running on the Windows host. Starting with Ansible 2.19 and the ``ansible.windows`` collection at ``3.1.0``, Ansible can now run on Windows hosts with WDAC enabled. .. admonition:: Experimental functionality - The App Control implementation is considered an experimental feature and can change in future releases. It is not possible to ensure all PowerShell modules will work with App Control enabled and that a module might enable arbitrary code to run in a way not typically allowed by App Control. It is recommended to test all modules with WDAC enabled before using them in production. + + The App Control implementation is considered an experimental feature and can change in future releases. It is not possible to ensure all PowerShell modules will work with App Control enabled and that a module might enable arbitrary code to run in a way not typically allowed by App Control. It is recommended to test all modules with WDAC enabled before using them in production. .. contents:: :local: From 642ccc1ec2a49a5aac20bbdfb0bc285a34936155 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Wed, 16 Jul 2025 12:23:36 +1000 Subject: [PATCH 10/10] Use github URL for PowerShell script --- docs/docsite/rst/os_guide/windows_app_control.rst | 2 +- .../scripts}/New-AnsiblePowerShellSignature.ps1 | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) rename {docs/docsite/rst/os_guide/powershell => examples/scripts}/New-AnsiblePowerShellSignature.ps1 (98%) diff --git a/docs/docsite/rst/os_guide/windows_app_control.rst b/docs/docsite/rst/os_guide/windows_app_control.rst index a7f81c60820..36e3e3ea3e8 100644 --- a/docs/docsite/rst/os_guide/windows_app_control.rst +++ b/docs/docsite/rst/os_guide/windows_app_control.rst @@ -59,7 +59,7 @@ Once the policy is created and the certificate that will be used to sign the Ans How to Sign Ansible Content --------------------------- -Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The PowerShell script `New-AnsiblePowerShellSignature.ps1 `_ can be used to sign both the execution wrapper used by Ansible to invoke modules and any PowerShell modules inside an Ansible collection. It requires the following to run: +Once the code signing certificate has been generated and trusted by the Windows host, it can be used to sign the scripts that Ansible will run. The PowerShell script `New-AnsiblePowerShellSignature.ps1 `_ can be used to sign both the execution wrapper used by Ansible to invoke modules and any PowerShell modules inside an Ansible collection. It requires the following to run: * PowerShell 7.4 or later * The `OpenAuthenticode `_ PowerShell module diff --git a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 b/examples/scripts/New-AnsiblePowerShellSignature.ps1 similarity index 98% rename from docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 rename to examples/scripts/New-AnsiblePowerShellSignature.ps1 index e6d73c1e228..b6d68c76aa1 100644 --- a/docs/docsite/rst/os_guide/powershell/New-AnsiblePowerShellSignature.ps1 +++ b/examples/scripts/New-AnsiblePowerShellSignature.ps1 @@ -20,6 +20,10 @@ Function New-AnsiblePowerShellSignature { '*.authenticode' signature file for the exec_wrapper.ps1 used inside Ansible itself. + This script should not be used directly from this URL. Download a copy of + the script and store it in a location that you control. It is possible a + future version of this script at this URL will include a breaking change. + .PARAMETER Certificate The certificate to use for signing the content.