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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
||
function Get-IniFile { | ||
param ( | ||
[parameter(mandatory=$true, position=0, valuefrompipelinebypropertyname=$true, valuefrompipeline=$true)][string]$FilePath | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 @{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
changed = $false | ||
}; | ||
|
||
$category = Get-Attr $params "category" -failifempty $true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have a look at |
||
$key = Get-Attr $params "key" -failifempty $true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change to |
||
$value = Get-Attr $params "value" -failifempty $true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change to |
||
$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" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
''' |
There was a problem hiding this comment.
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