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

win_secedit: Added module with tests/diff mode #26332

Merged
merged 5 commits into from
Jul 14, 2017
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
203 changes: 203 additions & 0 deletions lib/ansible/modules/windows/win_security_policy.ps1
Original file line number Diff line number Diff line change
@@ -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
130 changes: 130 additions & 0 deletions lib/ansible/modules/windows/win_security_policy.py
Original file line number Diff line number Diff line change
@@ -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
'''
1 change: 1 addition & 0 deletions test/integration/targets/win_security_policy/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
windows/ci/group1
Original file line number Diff line number Diff line change
@@ -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
Loading