win_secedit: Added module with tests/diff mode (#26332)

* win_secedit: Added module with tests/diff mode

* fixed up test issues

* Added missing return value

* change for win_secedit based on review

* updated win_security_policy examples for rename
This commit is contained in:
Jordan Borean 2017-07-15 04:00:29 +10:00 committed by Matt Davis
parent 53295b2cbf
commit 8e05d7d962
6 changed files with 561 additions and 0 deletions

View file

@ -0,0 +1,203 @@
#!powershell
# This file is part of Ansible
#
# Copyright 2017, Jordan Borean <jborean93@gmail.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
$ErrorActionPreference = 'Stop'
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$diff_mode = Get-AnsibleParam -obj $Params -name "_ansible_diff" -type "bool" -default $false
$section = Get-AnsibleParam -obj $params -name "section" -type "str" -failifempty $true
$key = Get-AnsibleParam -obj $params -name "key" -type "str" -failifempty $true
$value = Get-AnsibleParam -obj $params -name "value" -failifempty $true
$result = @{
changed = $false
section = $section
key = $key
value = $value
}
if ($diff_mode) {
$result.diff = @{}
}
Function Run-SecEdit($arguments) {
$rc = $null
$stdout = $null
$stderr = $null
$log_path = [IO.Path]::GetTempFileName()
$arguments = $arguments + @("/log", $log_path)
try {
$stdout = &SecEdit.exe $arguments | Out-String
} catch {
$stderr = $_.Exception.Message
}
$log = Get-Content -Path $log_path
Remove-Item -Path $log_path -Force
$return = @{
log = ($log -join "`n").Trim()
stdout = $stdout
stderr = $stderr
rc = $LASTEXITCODE
}
return $return
}
Function Export-SecEdit() {
$secedit_ini_path = [IO.Path]::GetTempFileName()
# while this will technically make a change to the system in check mode by
# creating a new file, we need these values to be able to do anything
# substantial in check mode
$export_result = Run-SecEdit -arguments @("/export", "/cfg", $secedit_ini_path, "/quiet")
# check the return code and if the file has been populated, otherwise error out
if (($export_result.rc -ne 0) -or ((Get-Item -Path $secedit_ini_path).Length -eq 0)) {
Remove-Item -Path $secedit_ini_path -Force
$result.rc = $export_result.rc
$result.stdout = $export_result.stdout
$result.stderr = $export_result.stderr
Fail-Json $result "Failed to export secedit.ini file to $($secedit_ini_path)"
}
$secedit_ini = ConvertFrom-Ini -file_path $secedit_ini_path
return $secedit_ini
}
Function Import-SecEdit($ini) {
$secedit_ini_path = [IO.Path]::GetTempFileName()
$secedit_db_path = [IO.Path]::GetTempFileName()
Remove-Item -Path $secedit_db_path -Force # needs to be deleted for SecEdit.exe /import to work
$ini_contents = ConvertTo-Ini -ini $ini
Set-Content -Path $secedit_ini_path -Value $ini_contents
$result.changed = $true
$import_result = Run-SecEdit -arguments @("/configure", "/db", $secedit_db_path, "/cfg", $secedit_ini_path, "/quiet")
$result.import_log = $import_result.log
Remove-Item -Path $secedit_ini_path -Force
if ($import_result.rc -ne 0) {
$result.rc = $import_result.rc
$result.stdout = $import_result.stdout
$result.stderr = $import_result.stderr
Fail-Json $result "Failed to import secedit.ini file from $($secedit_ini_path)"
}
}
Function ConvertTo-Ini($ini) {
$content = @()
foreach ($key in $ini.GetEnumerator()) {
$section = $key.Name
$values = $key.Value
$content += "[$section]"
foreach ($value in $values.GetEnumerator()) {
$value_key = $value.Name
$value_value = $value.Value
if ($value_value -ne $null) {
$content += "$value_key = $value_value"
}
}
}
return $content -join "`r`n"
}
Function ConvertFrom-Ini($file_path) {
$ini = @{}
switch -Regex -File $file_path {
"^\[(.+)\]" {
$section = $matches[1]
$ini.$section = @{}
}
"(.+?)\s*=(.*)" {
$name = $matches[1].Trim()
$value = $matches[2].Trim()
if ($value -match "^\d+$") {
$value = [int]$value
} elseif ($value.StartsWith('"') -and $value.EndsWith('"')) {
$value = $value.Substring(1, $value.Length - 2)
}
$ini.$section.$name = $value
}
}
return $ini
}
$will_change = $false
$secedit_ini = Export-SecEdit
if (-not ($secedit_ini.ContainsKey($section))) {
Fail-Json $result "The section '$section' does not exist in SecEdit.exe output ini"
}
if ($secedit_ini.$section.ContainsKey($key)) {
$current_value = $secedit_ini.$section.$key
if ($current_value -cne $value) {
if ($diff_mode) {
$result.diff.prepared = @"
[$section]
-$key = $current_value
+$key = $value
"@
}
$secedit_ini.$section.$key = $value
$will_change = $true
}
} else {
if ($diff_mode) {
$result.diff.prepared = @"
[$section]
+$key = $value
"@
}
$secedit_ini.$section.$key = $value
$will_change = $true
}
if ($will_change -eq $true) {
$result.changed = $true
if (-not $check_mode) {
Import-SecEdit -ini $secedit_ini
# secedit doesn't error out on improper entries, re-export and verify
# the changes occurred
$verification_ini = Export-SecEdit
$new_section_values = $verification_ini.$section
if ($new_section_values.ContainsKey($key)) {
$new_value = $new_section_values.$key
if ($new_value -cne $value) {
Fail-Json $result "Failed to change the value for key '$key' in section '$section', the value is still $new_value"
}
} else {
Fail-Json $result "The key '$key' in section '$section' is not a valid key, cannot set this value"
}
}
}
Exit-Json $result

View file

@ -0,0 +1,130 @@
#!/usr/bin/python
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub, actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.0',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_security_policy
version_added: '2.4'
short_description: changes local security policy settings
description:
- Allows you to set the local security policies that are configured by
SecEdit.exe.
notes:
- This module uses the SecEdit.exe tool to configure the values, more details
of the areas and keys that can be configured can be found here
U(https://msdn.microsoft.com/en-us/library/bb742512.aspx).
- If you are in a domain environment these policies may be set by a GPO policy,
this module can temporarily change these values but the GPO will override
it if the value differs.
- You can also run C(SecEdit.exe /export /cfg C:\temp\output.ini) to view the
current policies set on your system.
options:
section:
description:
- The ini section the key exists in.
- If the section does not exist then the module will return an error.
- Example sections to use are 'Account Policies', 'Local Policies',
'Event Log', 'Restricted Groups', 'System Services', 'Registry' and
'File System'
required: yes
key:
description:
- The ini key of the section or policy name to modify.
- The module will return an error if this key is invalid.
required: yes
value:
description:
- The value for the ini key or policy name.
- If the key takes in a boolean value then 0 = False and 1 = True.
required: yes
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: change the guest account name
win_security_policy:
section: System Access
key: NewGuestName
value: Guest Account
- name: set the maximum password age
win_security_policy:
section: System Access
key: MaximumPasswordAge
value: 15
- name: do not store passwords using reversible encryption
win_security_policy:
section: System Access
key: ClearTextPassword
value: 0
- name: enable system events
win_security_policy:
section: Event Audit
key: AuditSystemEvents
value: 1
'''
RETURN = r'''
rc:
description: The return code after a failure when running SecEdit.exe.
returned: failure with secedit calls
type: int
sample: -1
stdout:
description: The output of the STDOUT buffer after a failure when running
SecEdit.exe.
returned: failure with secedit calls
type: string
sample: check log for error details
stderr:
description: The output of the STDERR buffer after a failure when running
SecEdit.exe.
returned: failure with secedit calls
type: string
sample: failed to import security policy
import_log:
description: The log of the SecEdit.exe /configure job that configured the
local policies. This is used for debugging purposes on failures.
returned: secedit.exe /import run and change occurred
type: string
sample: Completed 6 percent (0/15) \tProcess Privilege Rights area.
key:
description: The key in the section passed to the module to modify.
returned: success
type: string
sample: NewGuestName
section:
description: The section passed to the module to modify.
returned: success
type: string
sample: System Access
value:
description: The value passed to the module to modify to.
returned: success
type: string
sample: Guest Account
'''

View file

@ -0,0 +1 @@
windows/ci/group1

View file

@ -0,0 +1,53 @@
#!powershell
# WANT_JSON
# POWERSHELL_COMMON
# basic script to get the lsit of users in a particular right
# this is quite complex to put as a simple script so this is
# just a simple module
$ErrorActionPreference = 'Stop'
$params = Parse-Args $args -supports_check_mode $false
$section = Get-AnsibleParam -obj $params -name "section" -type "str" -failifempty $true
$key = Get-AnsibleParam -obj $params -name "key" -type "str" -failifempty $true
$result = @{
changed = $false
}
Function ConvertFrom-Ini($file_path) {
$ini = @{}
switch -Regex -File $file_path {
"^\[(.+)\]" {
$section = $matches[1]
$ini.$section = @{}
}
"(.+?)\s*=(.*)" {
$name = $matches[1].Trim()
$value = $matches[2].Trim()
if ($value -match "^\d+$") {
$value = [int]$value
} elseif ($value.StartsWith('"') -and $value.EndsWith('"')) {
$value = $value.Substring(1, $value.Length - 2)
}
$ini.$section.$name = $value
}
}
$ini
}
$secedit_ini_path = [IO.Path]::GetTempFileName()
&SecEdit.exe /export /cfg $secedit_ini_path /quiet
$secedit_ini = ConvertFrom-Ini -file_path $secedit_ini_path
if ($secedit_ini.ContainsKey($section)) {
$result.value = $secedit_ini.$section.$key
} else {
$result.value = $null
}
Exit-Json $result

View file

@ -0,0 +1,41 @@
---
- name: get current entry for audit
test_win_security_policy:
section: Event Audit
key: AuditSystemEvents
register: before_value_audit
- name: get current entry for guest
test_win_security_policy:
section: System Access
key: NewGuestName
register: before_value_guest
- block:
- name: set AuditSystemEvents entry before tests
win_security_policy:
section: Event Audit
key: AuditSystemEvents
value: 0
- name: set NewGuestName entry before tests
win_security_policy:
section: System Access
key: NewGuestName
value: Guest
- name: run tests
include_tasks: tests.yml
always:
- name: reset entries for AuditSystemEvents
win_security_policy:
section: Event Audit
key: AuditSystemEvents
value: "{{before_value_audit.value}}"
- name: reset entries for NewGuestName
win_security_policy:
section: System Access
key: NewGuestName
value: "{{before_value_guest.value}}"

View file

@ -0,0 +1,133 @@
---
- name: fail with invalid section name
win_security_policy:
section: This is not a valid section
key: KeyName
value: 0
register: fail_invalid_section
failed_when: fail_invalid_section.msg != "The section 'This is not a valid section' does not exist in SecEdit.exe output ini"
- name: fail with invalid key name
win_security_policy:
section: System Access
key: InvalidKey
value: 0
register: fail_invalid_key
failed_when: fail_invalid_key.msg != "The key 'InvalidKey' in section 'System Access' is not a valid key, cannot set this value"
- name: change existing key check
win_security_policy:
section: Event Audit
key: AuditSystemEvents
value: 1
register: change_existing_check
check_mode: yes
- name: get actual change existing key check
test_win_security_policy:
section: Event Audit
key: AuditSystemEvents
register: change_existing_actual_check
- name: assert change existing key check
assert:
that:
- change_existing_check|changed
- change_existing_actual_check.value == 0
- name: change existing key
win_security_policy:
section: Event Audit
key: AuditSystemEvents
value: 1
register: change_existing
- name: get actual change existing key
test_win_security_policy:
section: Event Audit
key: AuditSystemEvents
register: change_existing_actual
- name: assert change existing key
assert:
that:
- change_existing|changed
- change_existing_actual.value == 1
- name: change existing key again
win_security_policy:
section: Event Audit
key: AuditSystemEvents
value: 1
register: change_existing_again
- name: assert change existing key again
assert:
that:
- not change_existing_again|changed
- change_existing_again.value == 1
- name: change existing key with string type
win_security_policy:
section: Event Audit
key: AuditSystemEvents
value: "1"
register: change_existing_key_with_type
- name: assert change existing key with string type
assert:
that:
- not change_existing_key_with_type|changed
- change_existing_key_with_type.value == "1"
- name: change existing string key check
win_security_policy:
section: System Access
key: NewGuestName
value: New Guest
register: change_existing_string_check
check_mode: yes
- name: get actual change existing string key check
test_win_security_policy:
section: System Access
key: NewGuestName
register: change_existing_string_actual_check
- name: assert change existing string key check
assert:
that:
- change_existing_string_check|changed
- change_existing_string_actual_check.value == "Guest"
- name: change existing string key
win_security_policy:
section: System Access
key: NewGuestName
value: New Guest
register: change_existing_string
- name: get actual change existing string key
test_win_security_policy:
section: System Access
key: NewGuestName
register: change_existing_string_actual
- name: assert change existing string key
assert:
that:
- change_existing_string|changed
- change_existing_string_actual.value == "New Guest"
- name: change existing string key again
win_security_policy:
section: System Access
key: NewGuestName
value: New Guest
register: change_existing_string_again
- name: assert change existing string key again
assert:
that:
- not change_existing_string_again|changed
- change_existing_string_again.value == "New Guest"