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 module: adds ability to mod local security policies #22775

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
179 changes: 179 additions & 0 deletions lib/ansible/modules/windows/win_secedit.ps1
@@ -0,0 +1,179 @@
#!powershell
# This file is part of Ansible
#
# Copyright 2016, Red Hat Inc
#
# 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

########


Set-StrictMode -Version Latest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually set StrictMode to 2 when running a module automatically, this should be needed


function Get-IniFile {
param (
[parameter(mandatory=$true, position=0, valuefrompipelinebypropertyname=$true, valuefrompipeline=$true)][string]$FilePath
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we able to just move this to function Get-IniFile($FilePath) to make it more python like

)

$ini = New-Object System.Collections.Specialized.OrderedDictionary
$currentSection = New-Object System.Collections.Specialized.OrderedDictionary
$curSectionName = "default"

switch -regex (gc $FilePath)
{
"^\[(?<Section>.*)\]"
{
$ini.Add($curSectionName, $currentSection)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want to remove an indent for these lines so they match up in each case statement


$curSectionName = $Matches['Section'].trim()
$currentSection = New-Object System.Collections.Specialized.OrderedDictionary
}
"(?<Key>.*)\=(?<Value>.*)"
{
# add to current section Hash Set
$currentSection.Add($Matches['Key'].trim(), $Matches['Value'].trim())
}
"^$"
{
# ignore blank line
}
default
{
throw "Unidentified: $_" # should not happen
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to Fail-Json @{} "Error Message here" and change the error to be a bit more descriptive

}
}
if ($ini.Keys -notcontains $curSectionName) { $ini.Add($curSectionName, $currentSection) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move the if body to a new line


return $ini
}

function Out-IniFile{
param (
[parameter(mandatory=$true, position=0, valuefrompipelinebypropertyname=$true, valuefrompipeline=$true)][System.Collections.Specialized.OrderedDictionary]$ini,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably want more input from the other Windows guys but I don't think we want to do pipelining in the modules if we can avoid it. I believe this is so we keep a similar standard between the python and powershell ones. @jhawkesworth @dagwieers @nitzmahone?

[parameter(mandatory=$false,position=1, valuefrompipelinebypropertyname=$true, valuefrompipeline=$false)][String]$FilePath
)

$output = ""
ForEach ($section in $ini.GetEnumerator())
{
if ($section.Name -ne "default")
{
# insert a blank line after a section
$sep = @{$true="";$false="`r`n"}[[String]::IsNullOrWhiteSpace($output)]
$output += "$sep[$($section.Name)]`r`n"
}
ForEach ($entry in $section.Value.GetEnumerator())
{
$sep = @{$true="";$false="="}[$entry.Name -eq ";"]
$output += "$($entry.Name)$sep$($entry.Value)`r`n"
}

}

$output = $output.TrimEnd("`r`n")
if ([String]::IsNullOrEmpty($FilePath))
{
return $output
}
else
{
$output | Out-File -FilePath $FilePath -Encoding:ASCII
}
}

$params = Parse-Args $args;

$result = New-Object psobject @{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$result = @{

changed = $false
};

$category = Get-Attr $params "category" -failifempty $true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a look at Get-AnsibleParam which can be seen in other modules like win_tempfile. This way you can specify the object type and other options.

$key = Get-Attr $params "key" -failifempty $true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to Get-AnsibleParam as above

$value = Get-Attr $params "value" -failifempty $true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to Get-AnsibleParam as above

$sepath = "$home\sec_edit_dump.inf"

If ((Get-WmiObject Win32_ComputerSystem).PartOfDomain) {
Fail-Json $result "This host is joined to a Domain Controller, you'll need to modify GPO directly instead of secedit"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be here, in some GPO policies I've seen set definitely allowed me to change certain entries just not others. This should probably be a warning in the python document.

}

SecEdit.exe /export /cfg $sepath /quiet

$ini = Get-IniFile -FilePath $sepath

Try {
$current_value = $ini.$category.$key
}
Catch {
If ($_.Exception.Message -like "*$category cannot be found*"){
$valid_categories = $ini.GETENUMERATOR() | % { $_.key + ',' }
Fail-Json $result "The category you specified, $category, is not valid. Valid categories for this system are $valid_categories."
}
ElseIf ($_.Exception.Message -like "*$key*"){
$valid_keys = $ini.$category.GETENUMERATOR() | % { $_.key + ',' }
Fail-Json $result "The key you specified, $key, is not valid. Valid keys for the category '$category' are: $valid_keys."
}
Else {
Fail-Json $result $_.Exception.Message
}
}

If ($current_value -eq $value) {
$result.msg = "Key Already Set"
$result.category = $category
$result.key = $key
$result.value = $value
}
Else {
$ini.$category.$key = $value
$ini | Out-IniFile -FilePath "$home\updated_inf"
SecEdit.exe /configure /db "$home\temp_db.sdb" /cfg "$home\updated_inf" /quiet
$result.changed = $true
$result.msg = "Key updated"
$result.category = $category
$result.key = $key
$result.value = $value
}

# Secedit doesnt error out when you try to configure it with bad values, even with /validate.
# The secedit behavior is to simply ignore invalid values and proceed anyways
# Here we are re exporting secedit to deterimine if the value 'stuck'
# If it is updated, then we're good to go. If it wasn't updated then we know
# that the supplied value was improper

SecEdit.exe /export /cfg $sepath /quiet

$updated_ini = Get-IniFile -FilePath $sepath

Try {
$updated_value = $updated_ini.$category.$key
If ($updated_value -ne $value){
Fail-Json $result "The value you supplied '$value' was not accepted by SecEdit. Ensure it is a valid value and try again. The original value of '$current_value' has been kept in tact"
}
}
Catch [System.Management.Automation.PropertyNotFoundException] {
# Keys are removed if value was empty or whitespace. Expected behavior.
# Rethrow exception if value wasn't empty or whitespace.
If (![String]::IsNullOrWhiteSpace($value)) {
throw
}
}

rm $home\updated_inf
rm $home\temp_db.sdb
rm $home\sec_edit_dump.inf

Exit-Json $result
101 changes: 101 additions & 0 deletions lib/ansible/modules/windows/win_secedit.py
@@ -0,0 +1,101 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright 2016, Red Hat Inc
#
# 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 = {'status': ['preview'],
'supported_by': 'community',
'metadata_version': '1.0'}

DOCUMENTATION = '''
---
module: win_secedit
version_added: '2.4'
short_description: Manipulate local security policies via secedit
description:
- Modifies key values for local security policies via secedit
options:
category:
description:
- The category you wish to modify a value under. This can things like System Access, Event Audit, etc.
If you supply an invalid category the module will error out and let you know what the valid categories are for that particular system.
required: true
default: null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If parameter is required, there is no default.

key:
description:
- The key under the category for which you wish to modify the value.
For example, under the System Access category there is a key MinimumPasswordAge that could be targeted.
Just like with category, if an invalid key is specified, the module will error out and show what the valid keys for the given category are.
required: true
default: null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

value:
description:
- The value to assign to the key.
required: true
default: null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

author:
- Jonathan Davila (@defionscode)
notes:
- If the target server is joined to a DC then it will error as any secedit modifications would be overwritten by GPO.
- SecEdit does not error out when you try to modify a key value with an improper value,
such as passing in a string when a valid value would be an integer.
- Therefore, this module will attempt to configure secedit with any value supplied, then it does a second dump of secedit
to see if the requested change persisted, if it did not then it errors out, otherwise it continues as normal.
'''

EXAMPLES = '''
# Set the local security policy so that the maximum password age is 30
- name: Set max pwd age to 30 days
win_secedit:
category: System Access
key: MaximumPasswordAge
value: 30

# Configure local policy to audit system events
- name: Ensure system events are audited
win_secedit:
category: Event Audit
key: AuditSystemEvents
value: 1
'''

RETURN = '''
msg:
description: whether the key was set or updated
returned: success or changed
type: string
sample: Key updated
category:
description: the category targeted
returned: success or changed
type: string
sample: Event Audit
key:
description: the key within the category targeted
returned: success or changed
type: string
sample: AuditAccountLogon
value:
description: the value of the key
returned: success or changed
type: string
sample: 1
'''