diff --git a/changelogs/fragments/365-win_domain_user-add-support-for-spns-kerberos.yml b/changelogs/fragments/365-win_domain_user-add-support-for-spns-kerberos.yml new file mode 100644 index 00000000..cd800361 --- /dev/null +++ b/changelogs/fragments/365-win_domain_user-add-support-for-spns-kerberos.yml @@ -0,0 +1,4 @@ +minor_changes: +- win_domain_user - Add support for managing service prinicpal names via the ``spn`` param + and principals allowed to delegate via the ``delegates`` param + (https://github.com/ansible-collections/community.windows/pull/365) diff --git a/plugins/modules/win_domain_user.ps1 b/plugins/modules/win_domain_user.ps1 index dba6d868..d0fc52ac 100644 --- a/plugins/modules/win_domain_user.ps1 +++ b/plugins/modules/win_domain_user.ps1 @@ -2,8 +2,8 @@ # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -#Requires -Module Ansible.ModuleUtils.Legacy #AnsibleRequires -CSharpUtil Ansible.AccessToken +#AnsibleRequires -CSharpUtil Ansible.Basic Function Test-Credential { param( @@ -55,75 +55,138 @@ Function Test-Credential { } } -try { - Import-Module ActiveDirectory -} -catch { - $msg = -join @( - "Failed to import ActiveDirectory PowerShell module. This module should be run on a domain controller, " - "and the ActiveDirectory module must be available." +$spec = @{ + options = @{ + name = @{ type = 'str'; required = $true } + state = @{ + type = "str" + choices = @('present', 'absent', 'query') + default = "present" + } + domain_username = @{ type = 'str' } + domain_password = @{ type = 'str'; no_log = $true } + domain_server = @{ type = 'str' } + groups_action = @{ + type = 'str' + choices = @('add', 'remove', 'replace') + default = 'replace' + } + spn_action = @{ + type = 'str' + choices = @('add', 'remove', 'replace') + default = 'replace' + } + spn = @{ + type = 'list' + elements = 'str' + aliases = @('spns') + } + description = @{ type = 'str' } + password = @{ type = 'str'; no_log = $true } + password_expired = @{ type = 'bool' } + password_never_expires = @{ type = 'bool' } + user_cannot_change_password = @{ type = 'bool' } + account_locked = @{ type = 'bool' } + groups = @{ type = 'list'; elements = 'str' } + enabled = @{ type = 'bool'; default = $true } + path = @{ type = 'str' } + upn = @{ type = 'str' } + sam_account_name = @{ type = 'str' } + identity = @{ type = 'str' } + firstname = @{ type = 'str' } + surname = @{ type = 'str'; aliases = @('lastname') } + company = @{ type = 'str' } + email = @{ type = 'str' } + street = @{ type = 'str' } + city = @{ type = 'str' } + state_province = @{ type = 'str' } + postal_code = @{ type = 'str' } + country = @{ type = 'str' } + attributes = @{ type = 'dict' } + delegates = @{ + type = 'list' + elements = 'str' + aliases = @('principals_allowed_to_delegate') + } + update_password = @{ + type = 'str' + choices = @('always', 'on_create', 'when_changed') + default = 'always' + } + } + required_together = @( + , @("domain_username", "domain_password") ) - Fail-Json $result $msg + supports_check_mode = $true } -$result = @{ - changed = $false - created = $false - password_updated = $false -} +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) +$check_mode = $module.CheckMode -$ErrorActionPreference = "Stop" +$module.Result.created = $false +$module.Result.password_updated = $false -$params = Parse-Args $args -supports_check_mode $true -$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false +try { + Import-Module ActiveDirectory +} +catch { + $msg = "Failed to import ActiveDirectory PowerShell module." + $module.FailJson($msg, $_) +} # Module control parameters -$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present", "absent", "query" -$update_password = Get-AnsibleParam -obj $params -name "update_password" -type "str" -default "always" -validateset "always", "on_create", "when_changed" -$groups_action = Get-AnsibleParam -obj $params -name "groups_action" -type "str" -default "replace" -validateset "add", "remove", "replace" -$domain_username = Get-AnsibleParam -obj $params -name "domain_username" -type "str" -$domain_password = Get-AnsibleParam -obj $params -name "domain_password" -type "str" -failifempty ($null -ne $domain_username) -$domain_server = Get-AnsibleParam -obj $params -name "domain_server" -type "str" +$state = $module.Params.state +$update_password = $module.Params.update_password +$groups_action = $module.Params.groups_action +$domain_username = $module.Params.domain_username +$domain_password = $module.Params.domain_password +$domain_server = $module.Params.domain_server # User account parameters -$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true -$description = Get-AnsibleParam -obj $params -name "description" -type "str" -$password = Get-AnsibleParam -obj $params -name "password" -type "str" -$password_expired = Get-AnsibleParam -obj $params -name "password_expired" -type "bool" -$password_never_expires = Get-AnsibleParam -obj $params -name "password_never_expires" -type "bool" -$user_cannot_change_password = Get-AnsibleParam -obj $params -name "user_cannot_change_password" -type "bool" -$account_locked = Get-AnsibleParam -obj $params -name "account_locked" -type "bool" -$groups = Get-AnsibleParam -obj $params -name "groups" -type "list" -$enabled = Get-AnsibleParam -obj $params -name "enabled" -type "bool" -default $true -$path = Get-AnsibleParam -obj $params -name "path" -type "str" -$upn = Get-AnsibleParam -obj $params -name "upn" -type "str" -$sam_account_name = Get-AnsibleParam -obj $params -name "sam_account_name" -type "str" -$identity = Get-AnsibleParam -obj $params -name "identity" -type "str" -default $sam_account_name - -if ($null -eq $identity) { $identity = $name } +$name = $module.Params.name +$description = $module.Params.description +$password = $module.Params.password +$password_expired = $module.Params.password_expired +$password_never_expires = $module.Params.password_never_expires +$user_cannot_change_password = $module.Params.user_cannot_change_password +$account_locked = $module.Params.account_locked +$groups = $module.Params.groups +$enabled = $module.Params.enabled +$path = $module.Params.path +$upn = $module.Params.upn +$spn = $module.Params.spn +$spn_action = $module.Params.spn_action +$sam_account_name = $module.Params.sam_account_name +$delegates = $module.Params.delegates +$identity = $module.Params.identity + +if ($null -eq $identity) { + $identity = $name +} # User informational parameters $user_info = @{ - GivenName = Get-AnsibleParam -obj $params -name "firstname" -type "str" - Surname = Get-AnsibleParam -obj $params -name "surname" -type "str" - Company = Get-AnsibleParam -obj $params -name "company" -type "str" - EmailAddress = Get-AnsibleParam -obj $params -name "email" -type "str" - StreetAddress = Get-AnsibleParam -obj $params -name "street" -type "str" - City = Get-AnsibleParam -obj $params -name "city" -type "str" - State = Get-AnsibleParam -obj $params -name "state_province" -type "str" - PostalCode = Get-AnsibleParam -obj $params -name "postal_code" -type "str" - Country = Get-AnsibleParam -obj $params -name "country" -type "str" + GivenName = $module.Params.firstname + Surname = $module.Params.surname + Company = $module.Params.company + EmailAddress = $module.Params.email + StreetAddress = $module.Params.street + City = $module.Params.city + State = $module.Params.state_province + PostalCode = $module.Params.postal_code + Country = $module.Params.country } # Additional attributes -$attributes = Get-AnsibleParam -obj $params -name "attributes" +$attributes = $module.Params.attributes # Parameter validation If ($null -ne $account_locked -and $account_locked) { - Fail-Json $result "account_locked must be set to 'no' if provided" + $module.FailJson("account_locked must be set to 'no' if provided") } + If (($null -ne $password_expired) -and ($null -ne $password_never_expires)) { - Fail-Json $result "password_expired and password_never_expires are mutually exclusive but have both been set" + $module.FailJson("password_expired and password_never_expires are mutually exclusive but have both been set") } $extra_args = @{} @@ -132,6 +195,7 @@ if ($null -ne $domain_username) { $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $domain_username, $domain_password $extra_args.Credential = $credential } + if ($null -ne $domain_server) { $extra_args.Server = $domain_server } @@ -139,7 +203,9 @@ if ($null -ne $domain_server) { Function Get-PrincipalGroup { Param ($identity, $args_extra) try { - $groups = Get-ADPrincipalGroupMembership -Identity $identity @args_extra -ErrorAction Stop + $groups = Get-ADPrincipalGroupMembership ` + -Identity $identity @args_extra ` + -ErrorAction Stop } catch { Add-Warning -obj $result -message "Failed to enumerate user groups but continuing on.: $($_.Exception.Message)" @@ -153,7 +219,9 @@ Function Get-PrincipalGroup { } try { - $user_obj = Get-ADUser -Identity $identity -Properties ('*', 'msDS-PrincipalName') @extra_args + $user_obj = Get-ADUser ` + -Identity $identity ` + -Properties ('*', 'msDS-PrincipalName') @extra_args $user_guid = $user_obj.ObjectGUID } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { @@ -162,9 +230,6 @@ catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { } If ($state -eq 'present') { - # Ensure user exists - $new_user = $false - # If the account does not exist, create it If (-not $user_obj) { $create_args = @{} @@ -179,23 +244,22 @@ If ($state -eq 'present') { If ($null -ne $sam_account_name) { $create_args.SamAccountName = $sam_account_name } + if ($null -ne $password) { + $create_args.AccountPassword = ConvertTo-SecureString $password -AsPlainText -Force + } $user_obj = New-ADUser @create_args -WhatIf:$check_mode -PassThru @extra_args $user_guid = $user_obj.ObjectGUID - $new_user = $true - $result.created = $true - $result.changed = $true + $module.Result.created = $true + $module.Result.changed = $true If ($check_mode) { - Exit-Json $result + $module.ExitJson() } $user_obj = Get-ADUser -Identity $user_guid -Properties ('*', 'msDS-PrincipalName') @extra_args } - - If ($password) { + ElseIf ($password) { # Don't unnecessary check for working credentials. # Set the password if we need to. - # For new_users there is also no difference between always and when_changed - # so we don't need to differentiate between this two states. - If ($new_user -or ($update_password -eq "always")) { + If ($update_password -eq "always") { $set_new_credentials = $true } elseif ($update_password -eq "when_changed") { @@ -214,14 +278,18 @@ If ($state -eq 'present') { If ($set_new_credentials) { $secure_password = ConvertTo-SecureString $password -AsPlainText -Force try { - Set-ADAccountPassword -Identity $user_guid -Reset:$true -Confirm:$false -NewPassword $secure_password -WhatIf:$check_mode @extra_args + Set-ADAccountPassword -Identity $user_guid ` + -Reset:$true ` + -Confirm:$false ` + -NewPassword $secure_password ` + -WhatIf:$check_mode @extra_args } catch { - Fail-Json $result "Failed to set password on account: $($_.Exception.Message)" + $module.FailJson("Failed to set password on account: $($_.Exception.Message)", $_) } $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.password_updated = $true - $result.changed = $true + $module.Result.password_updated = $true + $module.Result.changed = $true } } @@ -229,44 +297,104 @@ If ($state -eq 'present') { If (($null -ne $password_never_expires) -and ($password_never_expires -ne $user_obj.PasswordNeverExpires)) { Set-ADUser -Identity $user_guid -PasswordNeverExpires $password_never_expires -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } If (($null -ne $password_expired) -and ($password_expired -ne $user_obj.PasswordExpired)) { Set-ADUser -Identity $user_guid -ChangePasswordAtLogon $password_expired -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } If (($null -ne $user_cannot_change_password) -and ($user_cannot_change_password -ne $user_obj.CannotChangePassword)) { Set-ADUser -Identity $user_guid -CannotChangePassword $user_cannot_change_password -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } # Assign other account settings If (($null -ne $upn) -and ($upn -ne $user_obj.UserPrincipalName)) { Set-ADUser -Identity $user_guid -UserPrincipalName $upn -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } If (($null -ne $sam_account_name) -and ($sam_account_name -ne $user_obj.SamAccountName)) { Set-ADUser -Identity $user_guid -SamAccountName $sam_account_name -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } If (($null -ne $description) -and ($description -ne $user_obj.Description)) { Set-ADUser -Identity $user_guid -description $description -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } If ($enabled -ne $user_obj.Enabled) { Set-ADUser -Identity $user_guid -Enabled $enabled -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } If ((-not $account_locked) -and ($user_obj.LockedOut -eq $true)) { Unlock-ADAccount -Identity $user_guid -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true + } + If ($delegates) { + if (Compare-Object $delegates $user_obj.PrincipalsAllowedToDelegateToAccount) { + Set-ADUser -Identity $user_guid -PrincipalsAllowedToDelegateToAccount $delegates + $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args + $module.Result.changed = $true + } + } + + # configure service principal names + if ($null -ne $spn) { + $current_spn = [Array]$user_obj.ServicePrincipalNames + $desired_spn = [Array]$spn + $spn_diff = @() + + # generate a diff + $desired_spn | ForEach-Object { + if ($current_spn -contains $_) { + $spn_diff += $_ + } + } + + try { + switch ($spn_action) { + "add" { + # the current spn list does not have any spn's in the desired list + if (-not $spn_diff) { + Set-ADUser ` + -Identity $user_guid ` + -ServicePrincipalNames @{ Add = $(($spn | ForEach-Object { "$($_)" } )) } ` + -WhatIf:$check_mode @extra_args + $module.Result.changed = $true + } + } + "remove" { + # the current spn list does not have any differences + # that means we can remove the desired list + if ($spn_diff) { + Set-ADUser ` + -Identity $user_guid ` + -ServicePrincipalNames @{ Remove = $(($spn | ForEach-Object { "$($_)" } )) } ` + -WhatIf:$check_mode @extra_args + $module.Result.changed = $true + } + } + "replace" { + # the current and desired spn lists do not match + if (Compare-Object $current_spn $desired_spn) { + Set-ADUser ` + -Identity $user_guid ` + -ServicePrincipalNames @{ Replace = $(($spn | ForEach-Object { "$($_)" } )) } ` + -WhatIf:$check_mode @extra_args + $module.Result.changed = $true + } + } + } + } + catch { + $module.FailJson("Failed to $spn_action SPN(s)", $_) + } } # Set user information @@ -279,7 +407,7 @@ If ($state -eq 'present') { $set_args = $extra_args.Clone() $set_args.$key = $value Set-ADUser -Identity $user_guid -WhatIf:$check_mode @set_args - $result.changed = $true + $module.Result.changed = $true $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args } } @@ -287,11 +415,12 @@ If ($state -eq 'present') { # Set additional attributes $set_args = $extra_args.Clone() $run_change = $false + if ($null -ne $attributes) { $add_attributes = @{} $replace_attributes = @{} foreach ($attribute in $attributes.GetEnumerator()) { - $attribute_name = $attribute.Name + $attribute_name = $attribute.Key $attribute_value = $attribute.Value $valid_property = [bool]($user_obj.PSobject.Properties.name -eq $attribute_name) @@ -317,13 +446,12 @@ If ($state -eq 'present') { if ($run_change) { Set-ADUser -Identity $user_guid -WhatIf:$check_mode @set_args - $result.changed = $true + $module.Result.changed = $true $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args } - # Configure group assignment - If ($null -ne $groups) { + if ($null -ne $groups) { $group_list = $groups $groups = @() @@ -339,7 +467,7 @@ If ($state -eq 'present') { If (-not ($assigned_groups -Contains $group)) { Add-ADGroupMember -Identity $group -Members $user_guid -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } } } @@ -348,7 +476,7 @@ If ($state -eq 'present') { If ($assigned_groups -Contains $group) { Remove-ADGroupMember -Identity $group -Members $user_guid -Confirm:$false -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } } } @@ -357,27 +485,27 @@ If ($state -eq 'present') { If (($group -ne $user_obj.PrimaryGroup) -and -not ($groups -Contains $group)) { Remove-ADGroupMember -Identity $group -Members $user_guid -Confirm:$false -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } } Foreach ($group in $groups) { If (-not ($assigned_groups -Contains $group)) { Add-ADGroupMember -Identity $group -Members $user_guid -WhatIf:$check_mode @extra_args $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.changed = $true + $module.Result.changed = $true } } } } } } -ElseIf ($state -eq 'absent') { +elseif ($state -eq 'absent') { # Ensure user does not exist If ($user_obj) { Remove-ADUser $user_obj -Confirm:$false -WhatIf:$check_mode @extra_args - $result.changed = $true - If ($check_mode) { - Exit-Json $result + $module.Result.changed = $true + if ($check_mode) { + $module.ExitJson() } $user_obj = $null } @@ -385,34 +513,36 @@ ElseIf ($state -eq 'absent') { If ($user_obj) { $user_obj = Get-ADUser -Identity $user_guid -Properties * @extra_args - $result.name = $user_obj.Name - $result.firstname = $user_obj.GivenName - $result.surname = $user_obj.Surname - $result.enabled = $user_obj.Enabled - $result.company = $user_obj.Company - $result.street = $user_obj.StreetAddress - $result.email = $user_obj.EmailAddress - $result.city = $user_obj.City - $result.state_province = $user_obj.State - $result.country = $user_obj.Country - $result.postal_code = $user_obj.PostalCode - $result.distinguished_name = $user_obj.DistinguishedName - $result.description = $user_obj.Description - $result.password_expired = $user_obj.PasswordExpired - $result.password_never_expires = $user_obj.PasswordNeverExpires - $result.user_cannot_change_password = $user_obj.CannotChangePassword - $result.account_locked = $user_obj.LockedOut - $result.sid = [string]$user_obj.SID - $result.upn = $user_obj.UserPrincipalName - $result.sam_account_name = $user_obj.SamAccountName - $result.groups = Get-PrincipalGroup $user_guid $extra_args - $result.msg = "User '$name' is present" - $result.state = "present" + $module.Result.name = $user_obj.Name + $module.Result.firstname = $user_obj.GivenName + $module.Result.surname = $user_obj.Surname + $module.Result.enabled = $user_obj.Enabled + $module.Result.company = $user_obj.Company + $module.Result.street = $user_obj.StreetAddress + $module.Result.email = $user_obj.EmailAddress + $module.Result.city = $user_obj.City + $module.Result.state_province = $user_obj.State + $module.Result.country = $user_obj.Country + $module.Result.postal_code = $user_obj.PostalCode + $module.Result.distinguished_name = $user_obj.DistinguishedName + $module.Result.description = $user_obj.Description + $module.Result.password_expired = $user_obj.PasswordExpired + $module.Result.password_never_expires = $user_obj.PasswordNeverExpires + $module.Result.user_cannot_change_password = $user_obj.CannotChangePassword + $module.Result.account_locked = $user_obj.LockedOut + $module.Result.delegates = $user_obj.PrincipalsAllowedToDelegateToAccount + $module.Result.sid = [string]$user_obj.SID + $module.Result.spn = [Array]$user_obj.ServicePrincipalNames + $module.Result.upn = $user_obj.UserPrincipalName + $module.Result.sam_account_name = $user_obj.SamAccountName + $module.Result.groups = Get-PrincipalGroup $user_guid $extra_args + $module.Result.msg = "User '$name' is present" + $module.Result.state = "present" } -Else { - $result.name = $name - $result.msg = "User '$name' is absent" - $result.state = "absent" +else { + $module.Result.name = $name + $module.Result.msg = "User '$name' is absent" + $module.Result.state = "absent" } -Exit-Json $result +$module.ExitJson() diff --git a/plugins/modules/win_domain_user.py b/plugins/modules/win_domain_user.py index 8915f2f8..537115f1 100644 --- a/plugins/modules/win_domain_user.py +++ b/plugins/modules/win_domain_user.py @@ -42,7 +42,7 @@ - Note that there is not a way to lock an account as an administrator. - Accounts are locked due to user actions; as an admin, you may only unlock a locked account. - If you wish to administratively disable an account, set I(enabled) to C(no). - choices: [ no ] + type: bool description: description: - Description of the user @@ -55,6 +55,7 @@ I(groups_action=replace). - Note that users cannot be removed from their principal group (for example, "Domain Users"). type: list + elements: str groups_action: description: - If C(add), the user is added to each group in I(groups) where not already a member. @@ -64,6 +65,24 @@ type: str choices: [ add, remove, replace ] default: replace + spn: + description: + - Specifies the service principal name(s) for the account. This parameter sets the + ServicePrincipalNames property of the account. The LDAP display name (ldapDisplayName) + for this property is servicePrincipalName. + type: list + elements: str + aliases: [ spns ] + version_added: 1.10.0 + spn_action: + description: + - If C(add), the SPNs are added to the user. + - If C(remove), the SPNs are removed from the user. + - If C(replace), the defined set of SPN's overwrite the current set of SPNs. + type: str + choices: [ add, remove, replace ] + default: replace + version_added: 1.10.0 password: description: - Optionally set the user's password to this (plain text) value. @@ -103,6 +122,7 @@ description: - Configures the user's last name (surname). type: str + aliases: [ lastname ] company: description: - Configures the user's company name. @@ -157,13 +177,23 @@ be updated - you must delete (e.g., C(state=absent)) the user and then re-add the user with the appropriate path. type: str + delegates: + description: + - Specifies an array of principal objects. This parameter sets the + msDS-AllowedToActOnBehalfOfOtherIdentity attribute of a computer account + object. + - Must be specified as a distinguished name C(CN=shenetworks,CN=Users,DC=ansible,DC=test) + type: list + elements: str + aliases: [ principals_allowed_to_delegate ] + version_added: 1.10.0 attributes: description: - A dict of custom LDAP attributes to set on the user. - This can be used to set custom attributes that are not exposed as module parameters, e.g. C(telephoneNumber). - See the examples on how to format this parameter. - type: str + type: dict domain_username: description: - The username to use when interacting with AD. @@ -199,6 +229,7 @@ - module: community.windows.win_user_profile author: - Nick Chandler (@nwchandler) + - Joe Zollo (@zollo) ''' EXAMPLES = r''' @@ -244,6 +275,35 @@ community.windows.win_domain_user: name: bob state: absent + +- name: Ensure user has spn's defined + community.windows.win_domain_user: + name: liz.kenyon + spn: + - MSSQLSvc/us99db-svr95:1433 + - MSSQLSvc/us99db-svr95.vmware.com:1433 + +- name: Ensure user has spn added + community.windows.win_domain_user: + name: liz.kenyon + spn_action: add + spn: + - MSSQLSvc/us99db-svr95:2433 + +- name: Ensure user is created with delegates and spn's defined + community.windows.win_domain_user: + name: shmemmmy + password: The3rubberducki33! + state: present + groups: + - Domain Admins + - Enterprise Admins + delegates: + - CN=shenetworks,CN=Users,DC=ansible,DC=test + - CN=mk.ai,CN=Users,DC=ansible,DC=test + - CN=jessiedotjs,CN=Users,DC=ansible,DC=test + spn: + - MSSQLSvc/us99db-svr95:2433 ''' RETURN = r''' @@ -272,6 +332,15 @@ returned: always type: str sample: US +delegates: + description: Principals allowed to delegate + returned: always + type: list + elements: str + sample: + - CN=svc.tech.unicorn,CN=Users,DC=ansible,DC=test + - CN=geoff,CN=Users,DC=ansible,DC=test + version_added: 1.10.0 description: description: A description of the account returned: always @@ -332,6 +401,14 @@ returned: always type: str sample: S-1-5-21-2752426336-228313920-2202711348-1175 +spn: + description: The service principal names + returned: always + type: list + sample: + - HTTPSvc/ws1intel-svc1 + - HTTPSvc/ws1intel-svc1.vmware.com + version_added: 1.10.0 state: description: The state of the user account returned: always diff --git a/tests/integration/targets/win_domain_user/tasks/check_mode_test.yml b/tests/integration/targets/win_domain_user/tasks/check_mode_test.yml index e7f1faa7..75513542 100644 --- a/tests/integration/targets/win_domain_user/tasks/check_mode_test.yml +++ b/tests/integration/targets/win_domain_user/tasks/check_mode_test.yml @@ -1,8 +1,7 @@ --- - -- name: Create Jane(check_mode) +- name: Create Justi (check_mode) community.windows.win_domain_user: - name: Jane + name: Justi password: J@n3P4ssw0rd# state: present update_password: on_create @@ -16,8 +15,13 @@ check_mode: true - name: Sanity check on Check Mode - win_shell: | - Get-AdUser -Identity Jane + ansible.windows.win_powershell: + script: | + try { + Get-AdUser -Identity Justi + $Ansible.Failed = $true + } catch { + $Ansible.Failed = $false + } register: sanity_check - failed_when: "'NotFound' not in sanity_check.stderr" changed_when: false diff --git a/tests/integration/targets/win_domain_user/tasks/main.yml b/tests/integration/targets/win_domain_user/tasks/main.yml index 1ea02d5e..2edc6ce0 100644 --- a/tests/integration/targets/win_domain_user/tasks/main.yml +++ b/tests/integration/targets/win_domain_user/tasks/main.yml @@ -1,6 +1,18 @@ --- -- name: Run Tests - import_tasks: tests.yml +- name: Remove Users + win_domain_user: + name: "{{ item }}" + state: absent + loop: + - justi + - hana + - katie + +- name: Run Test Suite 1 + import_tasks: test1.yml + +- name: Run Test Suite 2 + import_tasks: test2.yml - name: Run Check Mode Tests - import_tasks: check_mode_test.yml \ No newline at end of file + import_tasks: check_mode_test.yml diff --git a/tests/integration/targets/win_domain_user/tasks/test1.yml b/tests/integration/targets/win_domain_user/tasks/test1.yml new file mode 100644 index 00000000..a5ba7095 --- /dev/null +++ b/tests/integration/targets/win_domain_user/tasks/test1.yml @@ -0,0 +1,76 @@ +--- +- name: Justi | Create User + win_domain_user: + name: Justi + upn: justi@ansible.test + password: c0dinGwithKI@ + state: present + update_password: on_create + password_never_expires: false + enabled: true + spn: + - MSSQLSvc/US99DBSVR1 + - MSSQLSvc/US99DBSVR1.vmware.com + - MSSQLSvc/US99DBSVR1.vmware.com:1433 + register: new_user_test + failed_when: new_user_test is not success + +- name: Justi | Create User (idempotence check) + win_domain_user: + name: Justi + upn: justi@ansible.test + password: c0dinGwithKI@ + state: present + update_password: on_create + password_never_expires: false + enabled: true + spn: + - MSSQLSvc/US99DBSVR1 + - MSSQLSvc/US99DBSVR1.vmware.com + - MSSQLSvc/US99DBSVR1.vmware.com:1433 + register: new_user_test_idempotent + failed_when: new_user_test_idempotent is changed + +- name: Justi | Update Password + win_domain_user: + name: Justi + password: al3x@ndriastEch! + state: present + update_password: always + password_never_expires: false + enabled: true + register: password_changed + failed_when: not password_changed.changed + +- name: Justi | Replace SPNs + win_domain_user: + name: Justi + state: present + spn: + - MSSQLSvc/ + - MSSQLSvc/US99DBSVR1.vmware.com + register: spn_changed + failed_when: not spn_changed.changed + +- name: Justi | Add SPN + win_domain_user: + name: Justi + state: present + spn_action: add + spn: + - MSSQLSvc/US99DBSVR1.vmware.com:2433 + register: add_spn_changed + failed_when: add_spn_changed is not changed + +- name: Assertions + assert: + that: + - new_user_test.changed + - new_user_test.created + - not new_user_test.password_never_expires + - not new_user_test_idempotent.changed + - new_user_test_idempotent.distinguished_name == "CN=Justi,CN=Users,DC=ansible,DC=test" + - password_changed.changed + - password_changed.password_updated + - spn_changed.changed + - add_spn_changed.changed diff --git a/tests/integration/targets/win_domain_user/tasks/test2.yml b/tests/integration/targets/win_domain_user/tasks/test2.yml new file mode 100644 index 00000000..a5030d26 --- /dev/null +++ b/tests/integration/targets/win_domain_user/tasks/test2.yml @@ -0,0 +1,138 @@ +--- +- name: Hana | Create User w/Invalid Password + win_domain_user: + name: hana + upn: hana@ansible.test + firstname: Hana + surname: Lytx + company: HelpMeExitVi Inc. + password: 123 + state: present + groups: + - Domain Admins + street: 123 TechTok St. + city: Sysengineer + state_province: OH + postal_code: 12345 + country: US + attributes: + telephoneNumber: 555-123456 + update_password: when_changed + password_never_expires: true + register: bad_password_test + failed_when: bad_password_test is success + +- name: Hana | Create User Again w/Valid Password + win_domain_user: + name: hana + upn: hana@ansible.test + firstname: Hana + surname: Lytx + company: HelpMeExitVi Inc. + password: h@nAlyTx18!X + state: present + groups: + - Domain Admins + street: 123 TechTok St. + city: Sysengineer + state_province: OH + postal_code: 12345 + country: US + attributes: + telephoneNumber: 555-123456 + update_password: when_changed + password_never_expires: true + register: good_password_test + failed_when: good_password_test is not success + +- name: Katie | Create User with Delegates + win_domain_user: + name: katie + firstname: Katie + surname: Kickscancer + password: SyNs@tI0N + update_password: on_create + state: present + delegates: + - CN=justi,CN=Users,DC=ansible,DC=test + spn: + - HTTPSvc/judge-svc1:80 + - HTTPSvc/gabrielle-svc1.vmware.com + register: delegates_test + failed_when: delegates_test is not success + +- name: Katie | Create User with Delegates (idempotence check) + win_domain_user: + name: katie + firstname: Katie + surname: Kickscancer + password: SyNs@tI0N + update_password: on_create + state: present + delegates: + - CN=justi,CN=Users,DC=ansible,DC=test + spn: + - HTTPSvc/judge-svc1:80 + - HTTPSvc/gabrielle-svc1.vmware.com + register: delegates_test_idempotent + failed_when: delegates_test_idempotent is changed + +- name: Katie | Remove SPN + win_domain_user: + name: katie + state: present + spn_action: remove + spn: + - HTTPSvc/gabrielle-svc1.vmware.com + register: remove_spn_test + failed_when: remove_spn_test is not changed + +- name: Katie | Remove SPN (idempotence check) + win_domain_user: + name: katie + state: present + spn_action: remove + spn: + - HTTPSvc/gabrielle-svc1.vmware.com + register: remove_spn_test_idempotent + failed_when: remove_spn_test_idempotent is changed + +- name: Hana | Remove User + win_domain_user: + name: hana + state: absent + register: user_removed + failed_when: user_removed is not changed + +- name: Hana | Remove User (idempotence check) + win_domain_user: + name: hana + state: absent + register: user_removed_idempotent + failed_when: user_removed_idempotent is changed + +- name: Remove Justi + win_domain_user: + name: justi + state: absent + +- name: Remove Katie + win_domain_user: + name: katie + state: absent + +- name: Assertions + assert: + that: + - delegates_test is success + - not delegates_test_idempotent.changed + - not bad_password_test.changed + - good_password_test.changed + - good_password_test.upn == "hana@ansible.test" + - good_password_test.password_never_expires + - good_password_test.company == "HelpMeExitVi Inc." + - not good_password_test.created + - good_password_test.password_updated + - user_removed.state == "absent" + - not user_removed_idempotent.changed + - remove_spn_test.spn == ['HTTPSvc/judge-svc1:80'] diff --git a/tests/integration/targets/win_domain_user/tasks/tests.yml b/tests/integration/targets/win_domain_user/tasks/tests.yml deleted file mode 100644 index b90ccfca..00000000 --- a/tests/integration/targets/win_domain_user/tasks/tests.yml +++ /dev/null @@ -1,121 +0,0 @@ ---- -- name: Create Jane - community.windows.win_domain_user: - name: Jane - password: J@n3P4ssw0rd# - state: present - update_password: on_create - account_locked: false - password_never_expires: false - enabled: true - register: new_user_test - failed_when: new_user_test is not success - -- name: Create Jane (idempotence check) - community.windows.win_domain_user: - name: Jane - password: J@n3P4ssw0rd# - state: present - update_password: on_create - account_locked: false - password_never_expires: false - enabled: true - register: new_user_test_idempotent - failed_when: new_user_test_idempotent is changed - -- name: Create Jane update password - community.windows.win_domain_user: - name: Jane - password: J@n3P4ssw0rd# - state: present - update_password: always - account_locked: false - password_never_expires: false - enabled: true - register: password_changed - failed_when: not password_changed.changed - -- name: Create user with invalid password - community.windows.win_domain_user: - name: bob - upn: Bob@ansible.test - firstname: Bob - surname: Smith - company: BobCo - password: 123 - state: present - groups: - - Domain Admins - street: 123 4th St. - city: Sometown - state_province: IN - postal_code: 12345 - country: US - attributes: - telephoneNumber: 555-123456 - update_password: when_changed - password_never_expires: true - register: bad_password_test - failed_when: bad_password_test is success - -- name: Create user again with valid password - community.windows.win_domain_user: - name: bob - upn: Bob@ansible.test - firstname: Bob - surname: Smith - company: BobCo - password: B0bP4ssw0rd - state: present - groups: - - Domain Admins - street: 123 4th St. - city: Sometown - state_province: IN - postal_code: 12345 - country: US - attributes: - telephoneNumber: 555-123456 - update_password: when_changed - password_never_expires: true - register: good_password_test - failed_when: good_password_test is not success - -- name: Remove bob - community.windows.win_domain_user: - name: bob - state: absent - register: user_removed - failed_when: not user_removed.changed - -- name: Remove bob (idempotence check) - community.windows.win_domain_user: - name: bob - state: absent - register: user_removed_idempotent - failed_when: user_removed_idempotent.changed - -- name: Remove Jane - community.windows.win_domain_user: - name: Jane - state: absent - -- name: Assertions - assert: - that: - - new_user_test.changed - - new_user_test.created - - not new_user_test.password_never_expires - - not new_user_test_idempotent.changed - - new_user_test_idempotent.distinguished_name == "CN=Jane,CN=Users,DC=ansible,DC=test" - - password_changed.changed - - password_changed.password_updated - - bad_password_test.changed - - bad_password_test.created - - good_password_test.changed - - good_password_test.upn == "Bob@ansible.test" - - good_password_test.password_never_expires - - good_password_test.company == "BobCo" - - not good_password_test.created - - good_password_test.password_updated - - user_removed.state == "absent"