Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix gMSA setup on Windows #1576

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/book/src/capi/windows/windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Ansible doesn't run on directly on Windows (wsl works) but can used to configure
## Set up Windows machine
Follow the [WinRM Setup](https://docs.ansible.com/ansible/latest/os_guide/windows_setup.html) in the Ansible documentation for configuring WinRM on the Windows machine. Note the [ConfigureRemotingForAnsible.ps1](https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1) is for development only. Refer to [Ansible WinRM documentation](https://docs.ansible.com/ansible/latest/user_guide/windows_winrm.html) for details for advance configuration.

After WinRM is installed you can edit or `/etc/ansible/hosts` file with the following:
After WinRM is installed you can edit the `/etc/ansible/hosts` file with the following:

```
[winhost]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
# limitations under the License.

# script modified from https://github.com/Azure/AgentBaker/blob/8d5323f3b1a622d558e624e5a6b0963229f80b2a/staging/cse/windows/configfunc.ps1 under MIT

$ErrorActionPreference = 'Stop'

function Enable-Privilege {
param($Privilege)
$Definition = @'
param($Privilege)
$Definition = @'
using System;
using System.Runtime.InteropServices;
public class AdjPriv {
Expand Down Expand Up @@ -55,80 +56,184 @@ function Enable-Privilege {
}
}
'@
$ProcessHandle = (Get-Process -id $pid).Handle
$type = Add-Type $definition -PassThru
$type[0]::EnablePrivilege($processHandle, $Privilege)
$ProcessHandle = (Get-Process -id $pid).Handle
$type = Add-Type $definition -PassThru
$type[0]::EnablePrivilege($processHandle, $Privilege)
}

function Aquire-Privilege {
param($Privilege)

write-output "Acquiring the $Privilege privilege"
$enablePrivilegeResponse=$false
for($i = 0; $i -lt 10; $i++) {
write-output "Retry $i : Trying to enable the $Privilege privilege"
$enablePrivilegeResponse = Enable-Privilege -Privilege "$Privilege" -ErrorAction 'Continue'
if ($enablePrivilegeResponse) {
break
}
Start-Sleep 1
$enablePrivilegeResponse = $false
for ($i = 0; $i -lt 10; $i++) {
write-output "Retry $i : Trying to enable the $Privilege privilege"
$enablePrivilegeResponse = Enable-Privilege -Privilege "$Privilege"
if ($enablePrivilegeResponse) {
break
}
Start-Sleep 1
}
if(!$enablePrivilegeResponse) {
write-output "Failed to enable the $Privilege privilege."
exit 1
if (!$enablePrivilegeResponse) {
write-error "Failed to enable the $Privilege privilege."
exit 1
}
}

# Enable the PowerShell privilege to set the registry permissions.
Aquire-Privilege -Privilege "SeTakeOwnershipPrivilege"
function Set-RegistryKeyPermissions {
param (
[string]$RegistryKeyPath,
[string]$TargetOwner = "BUILTIN\Administrators"
)

# Set the registry permissions.
write-output "Setting GMSA plugin registry permissions"
try {
$ccgKeyPath = "System\CurrentControlSet\Control\CCG\COMClasses"
$owner = [System.Security.Principal.NTAccount]"BUILTIN\Administrators"
try {
$owner = [System.Security.Principal.NTAccount]$TargetOwner

# Open the key with permission to take ownership
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey(
$ccgKeyPath,
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
[System.Security.AccessControl.RegistryRights]::TakeOwnership)
$RegistryKeyPath,
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
[System.Security.AccessControl.RegistryRights]::TakeOwnership)

if (-not $key) {
write-host "Failed to open registry key $RegistryKeyPath. Registry key does not exist."
return
}

# Get ACL and set owner
$acl = $key.GetAccessControl()
$originalOwner = $acl.owner
$acl.SetOwner($owner)
$key.SetAccessControl($acl)


# Reopen the key with permission to change permissions
$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey(
$ccgKeyPath,
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
[System.Security.AccessControl.RegistryRights]::ChangePermissions)
$RegistryKeyPath,
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
[System.Security.AccessControl.RegistryRights]::ChangePermissions)
$acl = $key.GetAccessControl()
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
$owner,
[System.Security.AccessControl.RegistryRights]::FullControl,
[System.Security.AccessControl.AccessControlType]::Allow)

# Remove any deny permissions
$RemoveAcl = $acl.Access | Where-Object { $_.AccessControlType -eq "Deny" }
if ($RemoveAcl) {
$Acl.RemoveAccessRule($RemoveAcl)
}

# Disable protection (enable inheritance)
$acl.SetAccessRuleProtection($false, $true) # False disables protection; true preserves existing entries

# Add a new access rule
$rule = New-Object System.Security.AccessControl.RegistryAccessRule (
$owner,
[System.Security.AccessControl.RegistryRights]::FullControl,
[System.Security.AccessControl.AccessControlType]::Allow
)
$acl.SetAccessRule($rule)

# Apply the updated ACL back to the registry key
$key.SetAccessControl($acl)
} catch {
write-output "Failed to set GMSA plugin registry permissions. $_"

return @{
OriginalOwner = $originalOwner
RegistryKey = $key
Rule = $rule
}
}
catch {
write-error "Failed to set GMSA plugin registry permissions. $_"
exit 1
}
}

# Set the appropriate registry values.
try {
write-output "Setting the appropriate GMSA plugin registry values"
reg.exe import "registerplugin.reg"
} catch {
write-output "Failed to set GMSA plugin registry values. $_"
function Restore-RegistryKeyOriginalAccess {
param (
[Microsoft.Win32.RegistryKey]$Key,
[System.Security.AccessControl.RegistryAccessRule]$Rule,
[string]$OriginalOwner
)

try {
$acl = $key.GetAccessControl()
$acl.RemoveAccessRule($rule) | Out-Null
$acl.SetOwner([System.Security.Principal.NTAccount]$originalowner)

# Apply the updated ACL to the key
$key.SetAccessControl($acl)
$key.close()
}
catch {
Write-Error "Failed to restore original registry access. $_"
exit 1
}
}

###############################################################
######################### MAIN SCRIPT #########################
###############################################################

# Check if the registerplugin.reg file exists
$pluginPath = "$PSScriptRoot\registerplugin.reg"
if (-not (Test-Path "$pluginPath")) {
write-error "Couldn't find file: $pluginPath"
exit 1
}

# Enable the PowerShell privilege to set the registry permissions
Aquire-Privilege -Privilege "SeTakeOwnershipPrivilege"

# Get the registry key paths from the plugin file to set permissions
[System.Array]$registryKeyPaths = @( "System\CurrentControlSet\Control\CCG\COMClasses" )
$registryKeyPaths += Get-Content -Path $pluginPath | ForEach-Object {
if ($_ -match '^\[HKEY_LOCAL_MACHINE\\(.*)]$') {
return $matches[1]
}
}

write-output "Restore original access to registry key"
$acl = $key.GetAccessControl()
$acl.RemoveAccessRule($rule)
$acl.SetOwner([System.Security.Principal.NTAccount]$originalowner)
Aquire-Privilege -Privilege "SeRestorePrivilege"
$key.SetAccessControl($acl)
$key.close()
[System.Array]$registryResults = @()
try {
# Set the registry owner and permissions
Write-Output "Setting registry owner and permissions"
foreach ($registryKeyPath in $registryKeyPaths) {
write-output "Setting permissions: { KeyPath: $RegistryKeyPath }"
$result = Set-RegistryKeyPermissions -RegistryKeyPath "$registryKeyPath"
$registryResults += $result
}

# HACK: Set the error action preference to 'Continue' to avoid script-terminating errors
# Restore the original error action preference after the registry import is done
# In Windows PowerShell (v5.1), 2>&1 redirection in the presence of $ErrorActionPreference = 'Stop'
# generates a script-terminating error if stderr output is written.
# https://github.com/PowerShell/PowerShell/issues/3996
# https://www.reddit.com/r/PowerShell/comments/16j43tx/howto_properly_capture_error_output_from_external/
$ErrorActionPreference = 'Continue'

# Import the registry values from the plugin file
Write-Output "Setting the appropriate GMSA plugin registry values"
$cmdOutput = reg.exe import "$pluginPath" 2>&1

# Reset the error action preference to 'Stop'
$ErrorActionPreference = 'Stop'

if ($LASTEXITCODE -ne 0) {
throw "Failed to import GMSA plugin registry values. $cmdOutput"
}
Write-Output "Successfully imported the GMSA plugin registry values"
}
catch {
Write-Error "Couldn't install the GMSA plugin. $_"
exit 1
}
finally {
# Restore the original registry permissions
Write-Output "Restoring original access to registry key"

# Acquire necessary privileges for restoring owner
Aquire-Privilege -Privilege SeRestorePrivilege

foreach ($result in $registryResults) {
Restore-RegistryKeyOriginalAccess -Key $result.RegistryKey -Rule $result.Rule -OriginalOwner $result.OriginalOwner
}
}


write-output "Successfully installed the GMSA plugin"
35 changes: 19 additions & 16 deletions images/capi/ansible/windows/roles/gmsa/tasks/gmsa_keyvault.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,22 @@
dest: "{{ kubernetes_install_path }}"

- name: Register gMSA Key Vault plugin
ansible.windows.win_shell: |
{{ kubernetes_install_path }}\install-gmsa-keyvault-plugin.ps1
- name: Install registry CCG logging manifest
ansible.windows.win_shell: |
wevtutil.exe um {{ kubernetes_install_path }}\CCGEvents.man
wevtutil.exe im {{ kubernetes_install_path }}\CCGEvents.man
- name: Install registry Key Vault plugin logging manifest
ansible.windows.win_shell: |
wevtutil.exe um {{ kubernetes_install_path }}\CCGAKVPluginEvents.man
wevtutil.exe im {{ kubernetes_install_path }}\CCGAKVPluginEvents.man
- name: Clean up gMSA install files
ansible.windows.win_shell: |
Remove-Item {{ kubernetes_install_path }}\CCGEvents.man
Remove-Item {{ kubernetes_install_path }}\CCGAKVPluginEvents.man
Remove-Item {{ kubernetes_install_path }}\registerplugin.reg
Remove-Item {{ kubernetes_install_path }}\install-gmsa-keyvault-plugin.ps1
block:
- name: Import gMSA Key Vault plugin
ansible.windows.win_shell: |
{{ kubernetes_install_path }}\install-gmsa-keyvault-plugin.ps1
- name: Install registry CCG logging manifest
ansible.windows.win_shell: |
wevtutil.exe um {{ kubernetes_install_path }}\CCGEvents.man
wevtutil.exe im {{ kubernetes_install_path }}\CCGEvents.man
- name: Install registry Key Vault plugin logging manifest
ansible.windows.win_shell: |
wevtutil.exe um {{ kubernetes_install_path }}\CCGAKVPluginEvents.man
wevtutil.exe im {{ kubernetes_install_path }}\CCGAKVPluginEvents.man
always:
- name: Cleanup gMSA install files
ansible.windows.win_shell: |
Remove-Item {{ kubernetes_install_path }}\CCGEvents.man
Remove-Item {{ kubernetes_install_path }}\CCGAKVPluginEvents.man
Remove-Item {{ kubernetes_install_path }}\registerplugin.reg
Remove-Item {{ kubernetes_install_path }}\install-gmsa-keyvault-plugin.ps1
4 changes: 0 additions & 4 deletions images/capi/packer/goss/goss-command.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -231,16 +231,12 @@ command:
stdout:
- "C:\\Windows\\System32\\CCGAKVPlugin.dll"
timeout: 30000
{{if ne .Vars.distribution_version "2025"}}
# TODO: [SEPTEMEBER 2024] Known issue in Windows Server 2025 preview image.
# WIP to fix the bug: The property value is null/empty
Key Vault gMSA CCG interface is registered:
exec: powershell -command "(Get-Item 'HKLM:SOFTWARE\Classes\Interface\{6ECDA518-2010-4437-8BC3-46E752B7B172}') | Ft -autosize -wrap"
exit-status: 0
stdout:
- "ICcgDomainAuthCredentials"
timeout: 30000
{{end}}
{{end}}

{{ if ne .Vars.ssh_source_url "" }}
Expand Down