diff --git a/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.SID.psm1 b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.SID.psm1 new file mode 100644 index 00000000000..e377d6c3825 --- /dev/null +++ b/lib/ansible/module_utils/powershell/Ansible.ModuleUtils.SID.psm1 @@ -0,0 +1,85 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +Function Convert-FromSID($sid) { + # Converts a SID to a Down-Level Logon name in the form of DOMAIN\UserName + # If the SID is for a local user or group then DOMAIN would be the server + # name. + + $account_object = New-Object System.Security.Principal.SecurityIdentifier($sid) + try { + $nt_account = $account_object.Translate([System.Security.Principal.NTAccount]) + } catch { + Fail-Json -obj @{} -message "failed to convert sid '$sid' to a logon name: $($_.Exception.Message)" + } + + return $nt_account.Value +} + +Function Convert-ToSID($account_name) { + # Converts an account name to a SID, it can take in the following forms + # UPN: + # principal@domain (Domain users only) + # Down-Level Login Name + # DOMAIN\principal (Domain) + # SERVERNAME\principal (Local) + # .\principal (Local) + # NT AUTHORITY\SYSTEM (Local Service Accounts) + # Login Name + # principal (Local/Local Service Accounts) + + if ($account_name -like "*\*") { + $account_name_split = $account_name -split "\\" + if ($account_name_split[0] -eq ".") { + $domain = $env:COMPUTERNAME + } else { + $domain = $account_name_split[0] + } + $username = $account_name_split[1] + } elseif ($account_name -like "*@*") { + $account_name_split = $account_name -split "@" + $domain = $account_name_split[1] + $username = $account_name_split[0] + } else { + $domain = $null + $username = $account_name + } + + if ($domain) { + # searching for a local group with the servername prefixed will fail, + # need to check for this situation and only use NTAccount(String) + if ($domain -eq $env:COMPUTERNAME) { + $adsi = [ADSI]("WinNT://$env:COMPUTERNAME,computer") + $group = $adsi.psbase.children | Where-Object { $_.schemaClassName -eq "group" -and $_.Name -eq $username } + } else { + $group = $null + } + if ($group) { + $account = New-Object System.Security.Principal.NTAccount($username) + } else { + $account = New-Object System.Security.Principal.NTAccount($domain, $username) + } + } else { + # when in a domain NTAccount(String) will favour domain lookups check + # if username is a local user and explictly search on the localhost for + # that account + $adsi = [ADSI]("WinNT://$env:COMPUTERNAME,computer") + $user = $adsi.psbase.children | Where-Object { $_.schemaClassName -eq "user" -and $_.Name -eq $username } + if ($user) { + $account = New-Object System.Security.Principal.NTAccount($env:COMPUTERNAME, $username) + } else { + $account = New-Object System.Security.Principal.NTAccount($username) + } + } + + try { + $account_sid = $account.Translate([System.Security.Principal.SecurityIdentifier]) + } catch { + Fail-Json @{} "account_name $account_name is not a valid account, cannot get SID: $($_.Exception.Message)" + } + + return $account_sid.Value +} + +# this line must stay at the bottom to ensure all defined module parts are exported +Export-ModuleMember -Alias * -Function * -Cmdlet * diff --git a/test/integration/targets/win_module_utils/library/sid_utils_test.ps1 b/test/integration/targets/win_module_utils/library/sid_utils_test.ps1 new file mode 100644 index 00000000000..1b9bffd2f77 --- /dev/null +++ b/test/integration/targets/win_module_utils/library/sid_utils_test.ps1 @@ -0,0 +1,66 @@ +#!powershell + +#Requires -Module Ansible.ModuleUtils.Legacy +#Requires -Module Ansible.ModuleUtils.SID + +Function Assert-Equals($actual, $expected) { + if ($actual -ne $expected) { + Fail-Json @{} "actual != expected`nActual: $actual`nExpected: $expected" + } +} + +Function Get-ComputerSID() { + # this is sort off cheating but I can't see any better way of getting this SID + $admin_sid = Convert-ToSID -account_name "$env:COMPUTERNAME\Administrator" + + return $admin_sid.Substring(0, $admin_sid.Length - 4) +} + +$local_sid = Get-ComputerSID + +### Set this to the NETBIOS name of the domain you wish to test, not set for shippable ### +$test_domain = $null + +$tests = @( + # Local Users + @{ sid = "S-1-1-0"; full_name = "Everyone"; names = @("Everyone") }, + @{ sid = "S-1-5-18"; full_name = "NT AUTHORITY\SYSTEM"; names = @("NT AUTHORITY\SYSTEM", "SYSTEM") }, + @{ sid = "S-1-5-20"; full_name = "NT AUTHORITY\NETWORK SERVICE"; names = @("NT AUTHORITY\NETWORK SERVICE", "NETWORK SERVICE") }, + @{ sid = "$local_sid-500"; full_name = "$env:COMPUTERNAME\Administrator"; names = @("$env:COMPUTERNAME\Administrator", "Administrator", ".\Administrator") }, + + # Local Groups + @{ sid = "S-1-5-32-544"; full_name = "BUILTIN\Administrators"; names = @("BUILTIN\Administrators", "Administrators", ".\Administrators") } +) + +# Add domain tests if the domain name has been set +if ($test_domain -ne $null) { + Import-Module ActiveDirectory + $domain_info = Get-ADDomain -Identity $test_domain + $domain_sid = $domain_info.DomainSID + $domain_netbios = $domain_info.NetBIOSName + $domain_upn = $domain_info.Forest + + $tests += @{ + sid = "$domain_sid-512" + full_name = "$domain_netbios\Domain Admins" + names = @("$domain_netbios\Domain Admins", "Domain Admins@$domain_upn", "Domain Admins") + } + + $tests += @{ + sid = "$domain_sid-500" + full_name = "$domain_netbios\Administrator" + names = @("$domain_netbios\Administrator", "Administrator@$domain_upn") + } +} + +foreach ($test in $tests) { + $actual_account_name = Convert-FromSID -sid $test.sid + Assert-Equals -actual $actual_account_name -expected $test.full_name + + foreach ($test_name in $test.names) { + $actual_sid = Convert-ToSID -account_name $test_name + Assert-Equals -actual $actual_sid -expected $test.sid + } +} + +Exit-Json @{ data = "success" } diff --git a/test/integration/targets/win_module_utils/tasks/main.yml b/test/integration/targets/win_module_utils/tasks/main.yml index c3cfbc79cb4..3c1b9d7fa6c 100644 --- a/test/integration/targets/win_module_utils/tasks/main.yml +++ b/test/integration/targets/win_module_utils/tasks/main.yml @@ -39,3 +39,11 @@ - assert: that: - camel_conversion.data == 'success' + +- name: call module with SID tests + sid_utils_test: + register: sid_test + +- assert: + that: + - sid_test.data == 'success'