Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,511 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Jordan Borean <jborean93@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: psexec
short_description: Runs commands on a remote Windows host based on the PsExec
model
description:
- Runs a remote command from a Linux host to a Windows host without WinRM being
set up.
- Can be run on the Ansible controller to bootstrap Windows hosts to get them
ready for WinRM.
options:
hostname:
description:
- The remote Windows host to connect to, can be either an IP address or a
hostname.
type: str
required: yes
connection_username:
description:
- The username to use when connecting to the remote Windows host.
- This user must be a member of the C(Administrators) group of the Windows
host.
- Required if the Kerberos requirements are not installed or the username
is a local account to the Windows host.
- Can be omitted to use the default Kerberos principal ticket in the
local credential cache if the Kerberos library is installed.
- If I(process_username) is not specified, then the remote process will run
under a Network Logon under this account.
type: str
connection_password:
description:
- The password for I(connection_user).
- Required if the Kerberos requirements are not installed or the username
is a local account to the Windows host.
- Can be omitted to use a Kerberos principal ticket for the principal set
by I(connection_user) if the Kerberos library is installed and the
ticket has already been retrieved with the C(kinit) command before.
type: str
port:
description:
- The port that the remote SMB service is listening on.
type: int
default: 445
encrypt:
description:
- Will use SMB encryption to encrypt the SMB messages sent to and from the
host.
- This requires the SMB 3 protocol which is only supported from Windows
Server 2012 or Windows 8, older versions like Windows 7 or Windows Server
2008 (R2) must set this to C(no) and use no encryption.
- When setting to C(no), the packets are in plaintext and can be seen by
anyone sniffing the network, any process options are included in this.
type: bool
default: yes
connection_timeout:
description:
- The timeout in seconds to wait when receiving the initial SMB negotiate
response from the server.
type: int
default: 60
executable:
description:
- The executable to run on the Windows host.
type: str
required: yes
arguments:
description:
- Any arguments as a single string to use when running the executable.
type: str
working_directory:
description:
- Changes the working directory set when starting the process.
type: str
default: C:\Windows\System32
asynchronous:
description:
- Will run the command as a detached process and the module returns
immediately after starting the process while the process continues to
run in the background.
- The I(stdout) and I(stderr) return values will be null when this is set
to C(yes).
- The I(stdin) option does not work with this type of process.
- The I(rc) return value is not set when this is C(yes)
type: bool
default: no
load_profile:
description:
- Runs the remote command with the user's profile loaded.
type: bool
default: yes
process_username:
description:
- The user to run the process as.
- This can be set to run the process under an Interactive logon of the
specified account which bypasses limitations of a Network logon used when
this isn't specified.
- If omitted then the process is run under the same account as
I(connection_username) with a Network logon.
- Set to C(System) to run as the builtin SYSTEM account, no password is
required with this account.
- If I(encrypt) is C(no), the username and password are sent as a simple
XOR scrambled byte string that is not encrypted. No special tools are
required to get the username and password just knowledge of the protocol.
type: str
process_password:
description:
- The password for I(process_username).
- Required if I(process_username) is defined and not C(System).
type: str
integrity_level:
description:
- The integrity level of the process when I(process_username) is defined
and is not equal to C(System).
- When C(default), the default integrity level based on the system setup.
- When C(elevated), the command will be run with Administrative rights.
- When C(limited), the command will be forced to run with
non-Administrative rights.
type: str
choices:
- limited
- default
- elevated
default: default
interactive:
description:
- Will run the process as an interactive process that shows a process
Window of the Windows session specified by I(interactive_session).
- The I(stdout) and I(stderr) return values will be null when this is set
to C(yes).
- The I(stdin) option does not work with this type of process.
type: bool
default: no
interactive_session:
description:
- The Windows session ID to use when displaying the interactive process on
the remote Windows host.
- This is only valid when I(interactive) is C(yes).
- The default is C(0) which is the console session of the Windows host.
type: int
default: 0
priority:
description:
- Set the command's priority on the Windows host.
- See U(https://msdn.microsoft.com/en-us/library/windows/desktop/ms683211.aspx)
for more details.
type: str
choices:
- above_normal
- below_normal
- high
- idle
- normal
- realtime
default: normal
show_ui_on_logon_screen:
description:
- Shows the process UI on the Winlogon secure desktop when
I(process_username) is C(System).
type: bool
default: no
process_timeout:
description:
- The timeout in seconds that is placed upon the running process.
- A value of C(0) means no timeout.
type: int
default: 0
stdin:
description:
- Data to send on the stdin pipe once the process has started.
- This option has no effect when I(interactive) or I(asynchronous) is
C(yes).
type: str
requirements:
- pypsexec
- smbprotocol[kerberos] for optional Kerberos authentication
notes:
- This module requires the Windows host to have SMB configured and enabled,
and port 445 opened on the firewall.
- This module will wait until the process is finished unless I(asynchronous)
is C(yes), ensure the process is run as a non-interactive command to avoid
infinite hangs waiting for input.
- The I(connection_username) must be a member of the local Administrator group
of the Windows host. For non-domain joined hosts, the
C(LocalAccountTokenFilterPolicy) should be set to C(1) to ensure this works,
see U(https://support.microsoft.com/en-us/help/951016/description-of-user-account-control-and-remote-restrictions-in-windows).
- For more information on this module and the various host requirements, see
U(https://github.com/jborean93/pypsexec).
seealso:
- module: ansible.builtin.raw
- module: ansible.windows.win_command
- module: community.windows.win_psexec
- module: ansible.windows.win_shell
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Run a cmd.exe command
community.windows.psexec:
hostname: server
connection_username: username
connection_password: password
executable: cmd.exe
arguments: /c echo Hello World
- name: Run a PowerShell command
community.windows.psexec:
hostname: server.domain.local
connection_username: username@DOMAIN.LOCAL
connection_password: password
executable: powershell.exe
arguments: Write-Host Hello World
- name: Send data through stdin
community.windows.psexec:
hostname: 192.168.1.2
connection_username: username
connection_password: password
executable: powershell.exe
arguments: '-'
stdin: |
Write-Host Hello World
Write-Error Error Message
exit 0
- name: Run the process as a different user
community.windows.psexec:
hostname: server
connection_user: username
connection_password: password
executable: whoami.exe
arguments: /all
process_username: anotheruser
process_password: anotherpassword
- name: Run the process asynchronously
community.windows.psexec:
hostname: server
connection_username: username
connection_password: password
executable: cmd.exe
arguments: /c rmdir C:\temp
asynchronous: yes
- name: Use Kerberos authentication for the connection (requires smbprotocol[kerberos])
community.windows.psexec:
hostname: host.domain.local
connection_username: user@DOMAIN.LOCAL
executable: C:\some\path\to\executable.exe
arguments: /s
- name: Disable encryption to work with WIndows 7/Server 2008 (R2)
community.windows.psexec:
hostanme: windows-pc
connection_username: Administrator
connection_password: Password01
encrypt: no
integrity_level: elevated
process_username: Administrator
process_password: Password01
executable: powershell.exe
arguments: (New-Object -ComObject Microsoft.Update.Session).CreateUpdateInstaller().IsBusy
- name: Download and run ConfigureRemotingForAnsible.ps1 to setup WinRM
community.windows.psexec:
hostname: '{{ hostvars[inventory_hostname]["ansible_host"] | default(inventory_hostname) }}'
connection_username: '{{ ansible_user }}'
connection_password: '{{ ansible_password }}'
encrypt: yes
executable: powershell.exe
arguments: '-'
stdin: |
$ErrorActionPreference = "Stop"
$sec_protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
$sec_protocols = $sec_protocols -bor [Net.SecurityProtocolType]::Tls12
[Net.ServicePointManager]::SecurityProtocol = $sec_protocols
$url = "https://github.com/ansible/ansible/raw/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
Invoke-Expression ((New-Object Net.WebClient).DownloadString($url))
exit
delegate_to: localhost
'''
RETURN = r'''
msg:
description: Any exception details when trying to run the process
returned: module failed
type: str
sample: 'Received exception from remote PAExec service: Failed to start "invalid.exe". The system cannot find the file specified. [Err=0x2, 2]'
stdout:
description: The stdout from the remote process
returned: success and interactive or asynchronous is 'no'
type: str
sample: Hello World
stderr:
description: The stderr from the remote process
returned: success and interactive or asynchronous is 'no'
type: str
sample: Error [10] running process
pid:
description: The process ID of the asynchronous process that was created
returned: success and asynchronous is 'yes'
type: int
sample: 719
rc:
description: The return code of the remote process
returned: success and asynchronous is 'no'
type: int
sample: 0
'''
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_bytes, to_text
PYPSEXEC_IMP_ERR = None
try:
from pypsexec import client
from pypsexec.exceptions import PypsexecException, PAExecException, \
PDUException, SCMRException
from pypsexec.paexec import ProcessPriority
from smbprotocol.exceptions import SMBException, SMBAuthenticationError, \
SMBResponseException
import socket
HAS_PYPSEXEC = True
except ImportError:
PYPSEXEC_IMP_ERR = traceback.format_exc()
HAS_PYPSEXEC = False
KERBEROS_IMP_ERR = None
try:
import gssapi
# GSSAPI extension required for Kerberos Auth in SMB
from gssapi.raw import inquire_sec_context_by_oid
HAS_KERBEROS = True
except ImportError:
KERBEROS_IMP_ERR = traceback.format_exc()
HAS_KERBEROS = False
def remove_artifacts(module, client):
try:
client.remove_service()
except (SMBException, PypsexecException) as exc:
module.warn("Failed to cleanup PAExec service and executable: %s"
% to_text(exc))
def main():
module_args = dict(
hostname=dict(type='str', required=True),
connection_username=dict(type='str'),
connection_password=dict(type='str', no_log=True),
port=dict(type='int', required=False, default=445),
encrypt=dict(type='bool', default=True),
connection_timeout=dict(type='int', default=60),
executable=dict(type='str', required=True),
arguments=dict(type='str'),
working_directory=dict(type='str', default=r'C:\Windows\System32'),
asynchronous=dict(type='bool', default=False),
load_profile=dict(type='bool', default=True),
process_username=dict(type='str'),
process_password=dict(type='str', no_log=True),
integrity_level=dict(type='str', default='default',
choices=['default', 'elevated', 'limited']),
interactive=dict(type='bool', default=False),
interactive_session=dict(type='int', default=0),
priority=dict(type='str', default='normal',
choices=['above_normal', 'below_normal', 'high',
'idle', 'normal', 'realtime']),
show_ui_on_logon_screen=dict(type='bool', default=False),
process_timeout=dict(type='int', default=0),
stdin=dict(type='str')
)
result = dict(
changed=False,
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=False,
)
process_username = module.params['process_username']
process_password = module.params['process_password']
use_system = False
if process_username is not None and process_username.lower() == "system":
use_system = True
process_username = None
process_password = None
if process_username is not None and process_password is None:
module.fail_json(msg='parameters are required together when not '
'running as System: process_username, '
'process_password')
if not HAS_PYPSEXEC:
module.fail_json(msg=missing_required_lib("pypsexec"),
exception=PYPSEXEC_IMP_ERR)
hostname = module.params['hostname']
connection_username = module.params['connection_username']
connection_password = module.params['connection_password']
port = module.params['port']
encrypt = module.params['encrypt']
connection_timeout = module.params['connection_timeout']
executable = module.params['executable']
arguments = module.params['arguments']
working_directory = module.params['working_directory']
asynchronous = module.params['asynchronous']
load_profile = module.params['load_profile']
elevated = module.params['integrity_level'] == "elevated"
limited = module.params['integrity_level'] == "limited"
interactive = module.params['interactive']
interactive_session = module.params['interactive_session']
priority = {
"above_normal": ProcessPriority.ABOVE_NORMAL_PRIORITY_CLASS,
"below_normal": ProcessPriority.BELOW_NORMAL_PRIORITY_CLASS,
"high": ProcessPriority.HIGH_PRIORITY_CLASS,
"idle": ProcessPriority.IDLE_PRIORITY_CLASS,
"normal": ProcessPriority.NORMAL_PRIORITY_CLASS,
"realtime": ProcessPriority.REALTIME_PRIORITY_CLASS
}[module.params['priority']]
show_ui_on_logon_screen = module.params['show_ui_on_logon_screen']
process_timeout = module.params['process_timeout']
stdin = module.params['stdin']
if (connection_username is None or connection_password is None) and \
not HAS_KERBEROS:
module.fail_json(msg=missing_required_lib("gssapi"),
execption=KERBEROS_IMP_ERR)
win_client = client.Client(server=hostname, username=connection_username,
password=connection_password, port=port,
encrypt=encrypt)
try:
win_client.connect(timeout=connection_timeout)
except SMBAuthenticationError as exc:
module.fail_json(msg='Failed to authenticate over SMB: %s'
% to_text(exc))
except SMBResponseException as exc:
module.fail_json(msg='Received unexpected SMB response when opening '
'the connection: %s' % to_text(exc))
except PDUException as exc:
module.fail_json(msg='Received an exception with RPC PDU message: %s'
% to_text(exc))
except SCMRException as exc:
module.fail_json(msg='Received an exception when dealing with SCMR on '
'the Windows host: %s' % to_text(exc))
except (SMBException, PypsexecException) as exc:
module.fail_json(msg=to_text(exc))
except socket.error as exc:
module.fail_json(msg=to_text(exc))
# create PAExec service and run the process
result['changed'] = True
b_stdin = to_bytes(stdin, encoding='utf-8') if stdin else None
run_args = dict(
executable=executable, arguments=arguments, asynchronous=asynchronous,
load_profile=load_profile, interactive=interactive,
interactive_session=interactive_session,
run_elevated=elevated, run_limited=limited,
username=process_username, password=process_password,
use_system_account=use_system, working_dir=working_directory,
priority=priority, show_ui_on_win_logon=show_ui_on_logon_screen,
timeout_seconds=process_timeout, stdin=b_stdin
)
try:
win_client.create_service()
except (SMBException, PypsexecException) as exc:
module.fail_json(msg='Failed to create PAExec service: %s'
% to_text(exc))
try:
proc_result = win_client.run_executable(**run_args)
except (SMBException, PypsexecException) as exc:
module.fail_json(msg='Received error when running remote process: %s'
% to_text(exc))
finally:
remove_artifacts(module, win_client)
if asynchronous:
result['pid'] = proc_result[2]
elif interactive:
result['rc'] = proc_result[2]
else:
result['stdout'] = proc_result[0]
result['stderr'] = proc_result[1]
result['rc'] = proc_result[2]
# close the SMB connection
try:
win_client.disconnect()
except (SMBException, PypsexecException) as exc:
module.warn("Failed to close the SMB connection: %s" % to_text(exc))
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,132 @@
#!powershell
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.CommandUtil
$ErrorActionPreference = 'Stop'
$params = Parse-Args -arguments $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$results = @{
changed = $false
}
######################################
### populate sets for -validateset ###
######################################
$categories_rc = run-command -command 'auditpol /list /category /r'
$subcategories_rc = run-command -command 'auditpol /list /subcategory:* /r'
If ($categories_rc.item('rc') -eq 0) {
$categories = ConvertFrom-Csv $categories_rc.item('stdout') | Select-Object -expand Category*
}
Else {
Fail-Json -obj $results -message "Failed to retrive audit policy categories. Please make sure the auditpol command is functional on
the system and that the account ansible is running under is able to retrieve them. $($_.Exception.Message)"
}
If ($subcategories_rc.item('rc') -eq 0) {
$subcategories = ConvertFrom-Csv $subcategories_rc.item('stdout') | Select-Object -expand Category* |
Where-Object { $_ -notin $categories }
}
Else {
Fail-Json -obj $results -message "Failed to retrive audit policy subcategories. Please make sure the auditpol command is functional on
the system and that the account ansible is running under is able to retrieve them. $($_.Exception.Message)"
}
######################
### ansible params ###
######################
$category = Get-AnsibleParam -obj $params -name "category" -type "str" -ValidateSet $categories
$subcategory = Get-AnsibleParam -obj $params -name "subcategory" -type "str" -ValidateSet $subcategories
$audit_type = Get-AnsibleParam -obj $params -name "audit_type" -type "list" -failifempty -
########################
### Start Processing ###
########################
Function Get-AuditPolicy ($GetString) {
$auditpolcsv = Run-Command -command $GetString
If ($auditpolcsv.item('rc') -eq 0) {
$Obj = ConvertFrom-CSV $auditpolcsv.item('stdout') | Select-Object @{n = 'subcategory'; e = { $_.Subcategory.ToLower() } },
@{ n = 'audit_type'; e = { $_."Inclusion Setting".ToLower() } }
}
Else {
return $auditpolcsv.item('stderr')
}
$HT = @{}
Foreach ( $Item in $Obj ) {
$HT.Add($Item.subcategory, $Item.audit_type)
}
$HT
}
################
### Validate ###
################
#make sure category and subcategory are valid
If (-Not $category -and -Not $subcategory) { Fail-Json -obj $results -message "You must provide either a Category or Subcategory parameter" }
If ($category -and $subcategory) { Fail-Json -obj $results -message "Must pick either a specific subcategory or category. You cannot define both" }
$possible_audit_types = 'success', 'failure', 'none'
$audit_type | ForEach-Object {
If ($_ -notin $possible_audit_types) {
Fail-Json -obj $result -message "$_ is not a valid audit_type. Please choose from $($possible_audit_types -join ',')"
}
}
#############################################################
### build lists for setting, getting, and comparing rules ###
#############################################################
$audit_type_string = $audit_type -join ' and '
$SetString = 'auditpol /set'
$GetString = 'auditpol /get /r'
If ($category) { $SetString = "$SetString /category:`"$category`""; $GetString = "$GetString /category:`"$category`"" }
If ($subcategory) { $SetString = "$SetString /subcategory:`"$subcategory`""; $GetString = "$GetString /subcategory:`"$subcategory`"" }
Switch ($audit_type_string) {
'success and failure' { $SetString = "$SetString /success:enable /failure:enable"; $audit_type_check = $audit_type_string }
'failure' { $SetString = "$SetString /success:disable /failure:enable"; $audit_type_check = $audit_type_string }
'success' { $SetString = "$SetString /success:enable /failure:disable"; $audit_type_check = $audit_type_string }
'none' { $SetString = "$SetString /success:disable /failure:disable"; $audit_type_check = 'No Auditing' }
default { Fail-Json -obj $result -message "It seems you have specified an invalid combination of items for audit_type. Please review documentation" }
}
#########################
### check Idempotence ###
#########################
$CurrentRule = Get-AuditPolicy $GetString
#exit if the audit_type is already set properly for the category
If (-not ($CurrentRule.Values | Where-Object { $_ -ne $audit_type_check }) ) {
$results.current_audit_policy = Get-AuditPolicy $GetString
Exit-Json -obj $results
}
####################
### Apply Change ###
####################
If (-not $check_mode) {
$ApplyPolicy = Run-Command -command $SetString
If ($ApplyPolicy.Item('rc') -ne 0) {
$results.current_audit_policy = Get-AuditPolicy $GetString
Fail-Json $results "Failed to set audit policy - $($_.Exception.Message)"
}
}
$results.changed = $true
$results.current_audit_policy = Get-AuditPolicy $GetString
Exit-Json $results

View File

@@ -0,0 +1,69 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
# Copyright: (c) 2017, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: win_audit_policy_system
short_description: Used to make changes to the system wide Audit Policy
description:
- Used to make changes to the system wide Audit Policy.
options:
category:
description:
- Single string value for the category you would like to adjust the policy on.
- Cannot be used with I(subcategory). You must define one or the other.
- Changing this setting causes all subcategories to be adjusted to the defined I(audit_type).
type: str
subcategory:
description:
- Single string value for the subcategory you would like to adjust the policy on.
- Cannot be used with I(category). You must define one or the other.
type: str
audit_type:
description:
- The type of event you would like to audit for.
- Accepts a list. See examples.
type: list
elements: str
required: yes
choices: [ failure, none, success ]
notes:
- It is recommended to take a backup of the policies before adjusting them for the first time.
- See this page for in depth information U(https://technet.microsoft.com/en-us/library/cc766468.aspx).
seealso:
- module: community.windows.win_audit_rule
author:
- Noah Sparks (@nwsparks)
'''
EXAMPLES = r'''
- name: Enable failure auditing for the subcategory "File System"
community.windows.win_audit_policy_system:
subcategory: File System
audit_type: failure
- name: Enable all auditing types for the category "Account logon events"
community.windows.win_audit_policy_system:
category: Account logon events
audit_type: success, failure
- name: Disable auditing for the subcategory "File System"
community.windows.win_audit_policy_system:
subcategory: File System
audit_type: none
'''
RETURN = r'''
current_audit_policy:
description: details on the policy being targetted
returned: always
type: dict
sample: |-
{
"File Share":"failure"
}
'''

View File

@@ -0,0 +1,174 @@
#!powershell
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#Requires -Module Ansible.ModuleUtils.Legacy
#Requires -Module Ansible.ModuleUtils.SID
$params = Parse-Args -arguments $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
# module parameters
$path = Get-AnsibleParam -obj $params -name "path" -type "path" -failifempty $true -aliases "destination", "dest"
$user = Get-AnsibleParam -obj $params -name "user" -type "str" -failifempty $true
$rights = Get-AnsibleParam -obj $params -name "rights" -type "list"
$inheritance_flags = Get-AnsibleParam -obj $params -name "inheritance_flags" -type "list" -default 'ContainerInherit', 'ObjectInherit'
$prop_options = 'InheritOnly', 'None', 'NoPropagateInherit'
$propagation_flags = Get-AnsibleParam -obj $params -name "propagation_flags" -type "str" -default "none" -ValidateSet $prop_options
$audit_flags = Get-AnsibleParam -obj $params -name "audit_flags" -type "list" -default 'success'
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset 'present', 'absent'
#Make sure target path is valid
If (-not (Test-Path -Path $path) ) {
Fail-Json -obj $result -message "defined path ($path) is not found/invalid"
}
#function get current audit rules and convert to hashtable
Function Get-CurrentAuditRule ($path) {
Try {
$ACL = Get-Acl $path -Audit
}
Catch {
Return "Unable to retrieve the ACL on $Path"
}
$HT = Foreach ($Obj in $ACL.Audit) {
@{
user = $Obj.IdentityReference.ToString()
rights = ($Obj | Select-Object -expand "*rights").ToString()
audit_flags = $Obj.AuditFlags.ToString()
is_inherited = $Obj.IsInherited.ToString()
inheritance_flags = $Obj.InheritanceFlags.ToString()
propagation_flags = $Obj.PropagationFlags.ToString()
}
}
If (-Not $HT) {
"No audit rules defined on $path"
}
Else { $HT }
}
$result = @{
changed = $false
current_audit_rules = Get-CurrentAuditRule $path
}
#Make sure identity is valid and can be looked up
Try {
$SID = Convert-ToSid $user
}
Catch {
Fail-Json -obj $result -message "Failed to lookup the identity ($user) - $($_.exception.message)"
}
#get the path type
$ItemType = (Get-Item $path -Force).GetType()
switch ($ItemType) {
([Microsoft.Win32.RegistryKey]) { $registry = $true; $result.path_type = 'registry' }
([System.IO.FileInfo]) { $file = $true; $result.path_type = 'file' }
([System.IO.DirectoryInfo]) { $result.path_type = 'directory' }
}
#Get current acl/audit rules on the target
Try {
$ACL = Get-Acl $path -Audit
}
Catch {
Fail-Json -obj $result -message "Unable to retrieve the ACL on $Path - $($_.Exception.Message)"
}
#configure acl object to remove the specified user
If ($state -eq 'absent') {
#Try and find an identity on the object that matches user
#We skip inherited items since we can't remove those
$ToRemove = ($ACL.Audit | Where-Object { $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $SID -and
$_.IsInherited -eq $false }).IdentityReference
#Exit with changed false if no identity is found
If (-Not $ToRemove) {
$result.current_audit_rules = Get-CurrentAuditRule $path
Exit-Json -obj $result
}
#update the ACL object if identity found
Try {
$ToRemove | ForEach-Object { $ACL.PurgeAuditRules($_) }
}
Catch {
$result.current_audit_rules = Get-CurrentAuditRule $path
Fail-Json -obj $result -message "Failed to remove audit rule: $($_.Exception.Message)"
}
}
Else {
If ($registry) {
$PossibleRights = [System.Enum]::GetNames([System.Security.AccessControl.RegistryRights])
Foreach ($right in $rights) {
if ($right -notin $PossibleRights) {
Fail-Json -obj $result -message "$right does not seem to be a valid REGISTRY right"
}
}
$NewAccessRule = New-Object System.Security.AccessControl.RegistryAuditRule($user, $rights, $inheritance_flags, $propagation_flags, $audit_flags)
}
Else {
$PossibleRights = [System.Enum]::GetNames([System.Security.AccessControl.FileSystemRights])
Foreach ($right in $rights) {
if ($right -notin $PossibleRights) {
Fail-Json -obj $result -message "$right does not seem to be a valid FILE SYSTEM right"
}
}
If ($file -and $inheritance_flags -ne 'none') {
Fail-Json -obj $result -message "The target type is a file. inheritance_flags must be changed to 'none'"
}
$NewAccessRule = New-Object System.Security.AccessControl.FileSystemAuditRule($user, $rights, $inheritance_flags, $propagation_flags, $audit_flags)
}
#exit here if any existing rule matches defined rule since no change is needed
#if we need to ignore inherited rules in the future, this would be where to do it
#Just filter out inherited rules from $ACL.Audit
Foreach ($group in $ACL.Audit | Where-Object { $_.IsInherited -eq $false }) {
If (
($group | Select-Object -expand "*Rights") -eq ($NewAccessRule | Select-Object -expand "*Rights") -and
$group.AuditFlags -eq $NewAccessRule.AuditFlags -and
$group.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $SID -and
$group.InheritanceFlags -eq $NewAccessRule.InheritanceFlags -and
$group.PropagationFlags -eq $NewAccessRule.PropagationFlags
) {
$result.current_audit_rules = Get-CurrentAuditRule $path
Exit-Json -obj $result
}
}
#try and set the acl object. AddAuditRule allows for multiple entries to exist under the same
#identity...so if someone wanted success: write and failure: delete for example, that setup would be
#possible. The alternative is SetAuditRule which would instead modify an existing rule and not allow
#for setting the above example.
Try {
$ACL.AddAuditRule($NewAccessRule)
}
Catch {
Fail-Json -obj $result -message "Failed to set the audit rule: $($_.Exception.Message)"
}
}
#finally set the permissions
Try {
Set-Acl -Path $path -ACLObject $ACL -WhatIf:$check_mode
}
Catch {
$result.current_audit_rules = Get-CurrentAuditRule $path
Fail-Json -obj $result -message "Failed to apply audit change: $($_.Exception.Message)"
}
#exit here after a change is applied
$result.current_audit_rules = Get-CurrentAuditRule $path
$result.changed = $true
Exit-Json -obj $result

View File

@@ -0,0 +1,140 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Noah Sparks <nsparks@outlook.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: win_audit_rule
short_description: Adds an audit rule to files, folders, or registry keys
description:
- Used to apply audit rules to files, folders or registry keys.
- Once applied, it will begin recording the user who performed the operation defined into the Security
Log in the Event viewer.
- The behavior is designed to ignore inherited rules since those cannot be adjusted without first disabling
the inheritance behavior. It will still print inherited rules in the output though for debugging purposes.
options:
path:
description:
- Path to the file, folder, or registry key.
- Registry paths should be in Powershell format, beginning with an abbreviation for the root
such as, C(HKLM:\Software).
type: path
required: yes
aliases: [ dest, destination ]
user:
description:
- The user or group to adjust rules for.
type: str
required: yes
rights:
description:
- Comma separated list of the rights desired. Only required for adding a rule.
- If I(path) is a file or directory, rights can be any right under MSDN
FileSystemRights U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemrights.aspx).
- If I(path) is a registry key, rights can be any right under MSDN
RegistryRights U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.registryrights.aspx).
type: list
elements: str
required: yes
inheritance_flags:
description:
- Defines what objects inside of a folder or registry key will inherit the settings.
- If you are setting a rule on a file, this value has to be changed to C(none).
- For more information on the choices see MSDN PropagationFlags enumeration
at U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.inheritanceflags.aspx).
type: list
elements: str
choices: [ ContainerInherit, ObjectInherit ]
default: ContainerInherit,ObjectInherit
propagation_flags:
description:
- Propagation flag on the audit rules.
- This value is ignored when the path type is a file.
- For more information on the choices see MSDN PropagationFlags enumeration
at U(https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.propagationflags.aspx).
choices: [ None, InherityOnly, NoPropagateInherit ]
default: "None"
audit_flags:
description:
- Defines whether to log on failure, success, or both.
- To log both define as comma separated list "Success, Failure".
type: list
elements: str
required: yes
choices: [ Failure, Success ]
state:
description:
- Whether the rule should be C(present) or C(absent).
- For absent, only I(path), I(user), and I(state) are required.
- Specifying C(absent) will remove all rules matching the defined I(user).
type: str
choices: [ absent, present ]
default: present
seealso:
- module: community.windows.win_audit_policy_system
author:
- Noah Sparks (@nwsparks)
'''
EXAMPLES = r'''
- name: Add filesystem audit rule for a folder
community.windows.win_audit_rule:
path: C:\inetpub\wwwroot\website
user: BUILTIN\Users
rights: write,delete,changepermissions
audit_flags: success,failure
inheritance_flags: ContainerInherit,ObjectInherit
- name: Add filesystem audit rule for a file
community.windows.win_audit_rule:
path: C:\inetpub\wwwroot\website\web.config
user: BUILTIN\Users
rights: write,delete,changepermissions
audit_flags: success,failure
inheritance_flags: None
- name: Add registry audit rule
community.windows.win_audit_rule:
path: HKLM:\software
user: BUILTIN\Users
rights: delete
audit_flags: 'success'
- name: Remove filesystem audit rule
community.windows.win_audit_rule:
path: C:\inetpub\wwwroot\website
user: BUILTIN\Users
state: absent
- name: Remove registry audit rule
community.windows.win_audit_rule:
path: HKLM:\software
user: BUILTIN\Users
state: absent
'''
RETURN = r'''
current_audit_rules:
description:
- The current rules on the defined I(path)
- Will return "No audit rules defined on I(path)"
returned: always
type: dict
sample: |
{
"audit_flags": "Success",
"user": "Everyone",
"inheritance_flags": "False",
"is_inherited": "False",
"propagation_flags": "None",
"rights": "Delete"
}
path_type:
description:
- The type of I(path) being targetted.
- Will be one of file, directory, registry.
returned: always
type: str
'''

View File

@@ -0,0 +1,404 @@
#!powershell
# Copyright: (c) 2019, Prasoon Karunan V (@prasoonkarunan) <kvprasoon@Live.in>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# All helper methods are written in a binary module and has to be loaded for consuming them.
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.AddType
Set-StrictMode -Version 2.0
$spec = @{
options = @{
logon_count = @{ type = "int" }
password = @{ type = "str"; no_log = $true }
state = @{ type = "str"; choices = "absent", "present"; default = "present" }
username = @{ type = "str" }
}
required_if = @(
, @("state", "present", @("username", "password"))
)
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$logonCount = $module.Params.logon_count
$password = $module.Params.password
$state = $module.Params.state
$username = $module.Params.username
$domain = $null
if ($username) {
# Try and get the Netlogon form of the username specified. Translating to and from a SID gives us an NTAccount
# in the Netlogon form that we desire.
$ntAccount = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList $username
try {
$accountSid = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier])
}
catch [System.Security.Principal.IdentityNotMappedException] {
$module.FailJson("Failed to find a local or domain user with the name '$username'", $_)
}
$ntAccount = $accountSid.Translate([System.Security.Principal.NTAccount])
$domain, $username = $ntAccount.Value -split '\\'
}
# Make sure $null regardless of any input value if state: absent
if ($state -eq 'absent') {
$password = $null
}
Add-CSharpType -AnsibleModule $module -References @'
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
namespace Ansible.WinAutoLogon
{
internal class NativeHelpers
{
[StructLayout(LayoutKind.Sequential)]
public class LSA_OBJECT_ATTRIBUTES
{
public UInt32 Length = 0;
public IntPtr RootDirectory = IntPtr.Zero;
public IntPtr ObjectName = IntPtr.Zero;
public UInt32 Attributes = 0;
public IntPtr SecurityDescriptor = IntPtr.Zero;
public IntPtr SecurityQualityOfService = IntPtr.Zero;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
public static explicit operator string(LSA_UNICODE_STRING s)
{
byte[] strBytes = new byte[s.Length];
Marshal.Copy(s.Buffer, strBytes, 0, s.Length);
return Encoding.Unicode.GetString(strBytes);
}
public static SafeMemoryBuffer CreateSafeBuffer(string s)
{
if (s == null)
return new SafeMemoryBuffer(IntPtr.Zero);
byte[] stringBytes = Encoding.Unicode.GetBytes(s);
int structSize = Marshal.SizeOf(typeof(LSA_UNICODE_STRING));
IntPtr buffer = Marshal.AllocHGlobal(structSize + stringBytes.Length);
try
{
LSA_UNICODE_STRING lsaString = new LSA_UNICODE_STRING()
{
Length = (UInt16)(stringBytes.Length),
MaximumLength = (UInt16)(stringBytes.Length),
Buffer = IntPtr.Add(buffer, structSize),
};
Marshal.StructureToPtr(lsaString, buffer, false);
Marshal.Copy(stringBytes, 0, lsaString.Buffer, stringBytes.Length);
return new SafeMemoryBuffer(buffer);
}
catch
{
// Make sure we free the pointer before raising the exception.
Marshal.FreeHGlobal(buffer);
throw;
}
}
}
}
internal class NativeMethods
{
[DllImport("Advapi32.dll")]
public static extern UInt32 LsaClose(
IntPtr ObjectHandle);
[DllImport("Advapi32.dll")]
public static extern UInt32 LsaFreeMemory(
IntPtr Buffer);
[DllImport("Advapi32.dll")]
internal static extern Int32 LsaNtStatusToWinError(
UInt32 Status);
[DllImport("Advapi32.dll")]
public static extern UInt32 LsaOpenPolicy(
IntPtr SystemName,
NativeHelpers.LSA_OBJECT_ATTRIBUTES ObjectAttributes,
LsaPolicyAccessMask AccessMask,
out SafeLsaHandle PolicyHandle);
[DllImport("Advapi32.dll")]
public static extern UInt32 LsaRetrievePrivateData(
SafeLsaHandle PolicyHandle,
SafeMemoryBuffer KeyName,
out SafeLsaMemory PrivateData);
[DllImport("Advapi32.dll")]
public static extern UInt32 LsaStorePrivateData(
SafeLsaHandle PolicyHandle,
SafeMemoryBuffer KeyName,
SafeMemoryBuffer PrivateData);
}
internal class SafeLsaMemory : SafeBuffer
{
internal SafeLsaMemory() : base(true) { }
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
return NativeMethods.LsaFreeMemory(handle) == 0;
}
}
internal class SafeMemoryBuffer : SafeBuffer
{
internal SafeMemoryBuffer() : base(true) { }
internal SafeMemoryBuffer(IntPtr ptr) : base(true)
{
base.SetHandle(ptr);
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
if (handle != IntPtr.Zero)
Marshal.FreeHGlobal(handle);
return true;
}
}
public class SafeLsaHandle : SafeHandleZeroOrMinusOneIsInvalid
{
internal SafeLsaHandle() : base(true) { }
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
return NativeMethods.LsaClose(handle) == 0;
}
}
public class Win32Exception : System.ComponentModel.Win32Exception
{
private string _exception_msg;
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
public Win32Exception(int errorCode, string message) : base(errorCode)
{
_exception_msg = String.Format("{0} - {1} (Win32 Error Code {2}: 0x{3})", message, base.Message, errorCode, errorCode.ToString("X8"));
}
public override string Message { get { return _exception_msg; } }
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
[Flags]
public enum LsaPolicyAccessMask : uint
{
ViewLocalInformation = 0x00000001,
ViewAuditInformation = 0x00000002,
GetPrivateInformation = 0x00000004,
TrustAdmin = 0x00000008,
CreateAccount = 0x00000010,
CreateSecret = 0x00000020,
CreatePrivilege = 0x00000040,
SetDefaultQuotaLimits = 0x00000080,
SetAuditRequirements = 0x00000100,
AuditLogAdmin = 0x00000200,
ServerAdmin = 0x00000400,
LookupNames = 0x00000800,
Read = 0x00020006,
Write = 0x000207F8,
Execute = 0x00020801,
AllAccess = 0x000F0FFF,
}
public class LsaUtil
{
public static SafeLsaHandle OpenPolicy(LsaPolicyAccessMask access)
{
NativeHelpers.LSA_OBJECT_ATTRIBUTES oa = new NativeHelpers.LSA_OBJECT_ATTRIBUTES();
SafeLsaHandle lsaHandle;
UInt32 res = NativeMethods.LsaOpenPolicy(IntPtr.Zero, oa, access, out lsaHandle);
if (res != 0)
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
String.Format("LsaOpenPolicy({0}) failed", access.ToString()));
return lsaHandle;
}
public static string RetrievePrivateData(SafeLsaHandle handle, string key)
{
using (SafeMemoryBuffer keyBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(key))
{
SafeLsaMemory buffer;
UInt32 res = NativeMethods.LsaRetrievePrivateData(handle, keyBuffer, out buffer);
using (buffer)
{
if (res != 0)
{
// If the data object was not found we return null to indicate it isn't set.
if (res == 0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND
return null;
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
String.Format("LsaRetrievePrivateData({0}) failed", key));
}
NativeHelpers.LSA_UNICODE_STRING lsaString = (NativeHelpers.LSA_UNICODE_STRING)
Marshal.PtrToStructure(buffer.DangerousGetHandle(),
typeof(NativeHelpers.LSA_UNICODE_STRING));
return (string)lsaString;
}
}
}
public static void StorePrivateData(SafeLsaHandle handle, string key, string data)
{
using (SafeMemoryBuffer keyBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(key))
using (SafeMemoryBuffer dataBuffer = NativeHelpers.LSA_UNICODE_STRING.CreateSafeBuffer(data))
{
UInt32 res = NativeMethods.LsaStorePrivateData(handle, keyBuffer, dataBuffer);
if (res != 0)
{
// When clearing the private data with null it may return this error which we can ignore.
if (data == null && res == 0xC0000034) // STATUS_OBJECT_NAME_NOT_FOUND
return;
throw new Win32Exception(NativeMethods.LsaNtStatusToWinError(res),
String.Format("LsaStorePrivateData({0}) failed", key));
}
}
}
}
}
'@
$autoLogonRegPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'
$logonDetails = Get-ItemProperty -LiteralPath $autoLogonRegPath
$before = @{
state = 'absent'
}
if ('AutoAdminLogon' -in $logonDetails.PSObject.Properties.Name -and $logonDetails.AutoAdminLogon -eq 1) {
$before.state = 'present'
}
$mapping = @{
DefaultUserName = 'username'
DefaultDomainName = 'domain'
AutoLogonCount = 'logon_count'
}
foreach ($map_detail in $mapping.GetEnumerator()) {
if ($map_detail.Key -in $logonDetails.PSObject.Properties.Name) {
$before."$($map_detail.Value)" = $logonDetails."$($map_detail.Key)"
}
}
$module.Diff.before = $before
$propParams = @{
LiteralPath = $autoLogonRegPath
WhatIf = $module.CheckMode
Force = $true
}
# First set the registry information
# The DefaultPassword reg key should never be set, we use LSA to store the password in a more secure way.
if ('DefaultPassword' -in (Get-Item -LiteralPath $autoLogonRegPath).Property) {
# Bug on older Windows hosts where -WhatIf causes it fail to find the property
if (-not $module.CheckMode) {
Remove-ItemProperty -Name 'DefaultPassword' @propParams
}
$module.Result.changed = $true
}
$autoLogonKeyList = @{
DefaultUserName = @{
before = if ($before.ContainsKey('username')) { $before.username } else { $null }
after = $username
}
DefaultDomainName = @{
before = if ($before.ContainsKey('domain')) { $before.domain } else { $null }
after = $domain
}
AutoLogonCount = @{
before = if ($before.ContainsKey('logon_count')) { $before.logon_count } else { $null }
after = $logonCount
}
}
# Check AutoAdminLogon separately as it has different logic (key must exist)
if ($state -ne $before.state) {
$newValue = if ($state -eq 'present') { 1 } else { 0 }
$null = New-ItemProperty -Name 'AutoAdminLogon' -Value $newValue -PropertyType DWord @propParams
$module.Result.changed = $true
}
foreach ($key in $autoLogonKeyList.GetEnumerator()) {
$beforeVal = $key.Value.before
$after = $key.Value.after
if ($state -eq 'present' -and $beforeVal -cne $after) {
if ($null -ne $after) {
$null = New-ItemProperty -Name $key.Key -Value $after @propParams
}
elseif (-not $module.CheckMode) {
Remove-ItemProperty -Name $key.Key @propParams
}
$module.Result.changed = $true
}
elseif ($state -eq 'absent' -and $null -ne $beforeVal) {
if (-not $module.CheckMode) {
Remove-ItemProperty -Name $key.Key @propParams
}
$module.Result.changed = $true
}
}
# Finally update the password in the LSA private store.
$lsaHandle = [Ansible.WinAutoLogon.LsaUtil]::OpenPolicy('CreateSecret, GetPrivateInformation')
try {
$beforePass = [Ansible.WinAutoLogon.LsaUtil]::RetrievePrivateData($lsaHandle, 'DefaultPassword')
if ($beforePass -cne $password) {
# Due to .NET marshaling we need to pass in $null as NullString.Value so it's truly a null value.
if ($null -eq $password) {
$password = [NullString]::Value
}
if (-not $module.CheckMode) {
[Ansible.WinAutoLogon.LsaUtil]::StorePrivateData($lsaHandle, 'DefaultPassword', $password)
}
$module.Result.changed = $true
}
}
finally {
$lsaHandle.Dispose()
}
# Need to manually craft the after diff in case we are running in check mode
$module.Diff.after = @{
state = $state
}
if ($state -eq 'present') {
$module.Diff.after.username = $username
$module.Diff.after.domain = $domain
if ($null -ne $logonCount) {
$module.Diff.after.logon_count = $logonCount
}
}
$module.ExitJson()

View File

@@ -0,0 +1,72 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Prasoon Karunan V (@prasoonkarunan)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: win_auto_logon
short_description: Adds or Sets auto logon registry keys.
description:
- Used to apply auto logon registry setting.
options:
logon_count:
description:
- The number of times to do an automatic logon.
- This count is deremented by Windows everytime an automatic logon is
performed.
- Once the count reaches C(0) then the automatic logon process is
disabled.
type: int
username:
description:
- Username to login automatically.
- Must be set when C(state=present).
- This can be the Netlogon or UPN of a domain account and is
automatically parsed to the C(DefaultUserName) and C(DefaultDomainName)
registry properties.
type: str
password:
description:
- Password to be used for automatic login.
- Must be set when C(state=present).
- Value of this input will be used as password for I(username).
- While this value is encrypted by LSA it is decryptable to any user who
is an Administrator on the remote host.
type: str
state:
description:
- Whether the registry key should be C(present) or C(absent).
type: str
choices: [ absent, present ]
default: present
author:
- Prasoon Karunan V (@prasoonkarunan)
'''
EXAMPLES = r'''
- name: Set autologon for user1
community.windows.win_auto_logon:
username: User1
password: str0ngp@ssword
- name: Set autologon for abc.com\user1
community.windows.win_auto_logon:
username: abc.com\User1
password: str0ngp@ssword
- name: Remove autologon for user1
community.windows.win_auto_logon:
state: absent
- name: Set autologon for user1 with a limited logon count
community.windows.win_auto_logon:
username: User1
password: str0ngp@ssword
logon_count: 5
'''
RETURN = r'''
#
'''

View File

@@ -0,0 +1,115 @@
#!powershell
# Copyright: (c) 2019, Micah Hunsberger
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
function ConvertTo-Timestamp($start_date, $end_date) {
if ($start_date -and $end_date) {
return (New-TimeSpan -Start $start_date -End $end_date).TotalSeconds
}
}
function Format-Date([DateTime]$date) {
return $date.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssK')
}
function Get-CertificateInfo ($cert) {
$epoch_date = Get-Date -Date "01/01/1970"
$cert_info = @{ extensions = @() }
$cert_info.friendly_name = $cert.FriendlyName
$cert_info.thumbprint = $cert.Thumbprint
$cert_info.subject = $cert.Subject
$cert_info.issuer = $cert.Issuer
$cert_info.valid_from = (ConvertTo-Timestamp -start_date $epoch_date -end_date $cert.NotBefore.ToUniversalTime())
$cert_info.valid_from_iso8601 = Format-Date -date $cert.NotBefore
$cert_info.valid_to = (ConvertTo-Timestamp -start_date $epoch_date -end_date $cert.NotAfter.ToUniversalTime())
$cert_info.valid_to_iso8601 = Format-Date -date $cert.NotAfter
$cert_info.serial_number = $cert.SerialNumber
$cert_info.archived = $cert.Archived
$cert_info.version = $cert.Version
$cert_info.has_private_key = $cert.HasPrivateKey
$cert_info.issued_by = $cert.GetNameInfo('SimpleName', $true)
$cert_info.issued_to = $cert.GetNameInfo('SimpleName', $false)
$cert_info.signature_algorithm = $cert.SignatureAlgorithm.FriendlyName
$cert_info.dns_names = [System.Collections.Generic.List`1[String]]@($cert_info.issued_to)
$cert_info.raw = [System.Convert]::ToBase64String($cert.GetRawCertData())
$cert_info.public_key = [System.Convert]::ToBase64String($cert.GetPublicKey())
if ($cert.Extensions.Count -gt 0) {
[array]$cert_info.extensions = foreach ($extension in $cert.Extensions) {
$extension_info = @{
critical = $extension.Critical
field = $extension.Oid.FriendlyName
value = $extension.Format($false)
}
if ($extension -is [System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]) {
$cert_info.is_ca = $extension.CertificateAuthority
$cert_info.path_length_constraint = $extension.PathLengthConstraint
}
elseif ($extension -is [System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]) {
$cert_info.intended_purposes = $extension.EnhancedKeyUsages.FriendlyName -as [string[]]
}
elseif ($extension -is [System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]) {
$cert_info.key_usages = $extension.KeyUsages.ToString().Split(',').Trim() -as [string[]]
}
elseif ($extension -is [System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]) {
$cert_info.ski = $extension.SubjectKeyIdentifier
}
elseif ($extension.Oid.value -eq '2.5.29.17') {
$sans = $extension.Format($true).Split("`r`n", [System.StringSplitOptions]::RemoveEmptyEntries)
foreach ($san in $sans) {
$san_parts = $san.Split("=")
if ($san_parts.Length -ge 2 -and $san_parts[0].Trim() -eq 'DNS Name') {
$cert_info.dns_names.Add($san_parts[1].Trim())
}
}
}
$extension_info
}
}
return $cert_info
}
$store_location_values = ([System.Security.Cryptography.X509Certificates.StoreLocation]).GetEnumValues() | ForEach-Object { $_.ToString() }
$spec = @{
options = @{
thumbprint = @{ type = "str"; required = $false }
store_name = @{ type = "str"; default = "My"; }
store_location = @{ type = "str"; default = "LocalMachine"; choices = $store_location_values; }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$thumbprint = $module.Params.thumbprint
$store_name = $module.Params.store_name
$store_location = [System.Security.Cryptography.X509Certificates.Storelocation]"$($module.Params.store_location)"
$store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $store_name, $store_location
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
$module.Result.exists = $false
$module.Result.certificates = @()
try {
if ($null -ne $thumbprint) {
$found_certs = $store.Certificates.Find([System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint, $thumbprint, $false)
}
else {
$found_certs = $store.Certificates
}
if ($found_certs.Count -gt 0) {
$module.Result.exists = $true
[array]$module.Result.certificates = $found_certs | ForEach-Object { Get-CertificateInfo -cert $_ } | Sort-Object -Property { $_.thumbprint }
}
}
finally {
$store.Close()
}
$module.ExitJson()

View File

@@ -0,0 +1,228 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2016, Ansible, inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: win_certificate_info
short_description: Get information on certificates from a Windows Certificate Store
description:
- Returns information about certificates in a Windows Certificate Store.
options:
thumbprint:
description:
- The thumbprint as a hex string of a certificate to find.
- When specified, filters the I(certificates) return value to a single certificate
- See the examples for how to format the thumbprint.
type: str
required: no
store_name:
description:
- The name of the store to search.
- See U(https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.storename)
for a list of built-in store names.
type: str
default: My
store_location:
description:
- The location of the store to search.
type: str
choices: [ CurrentUser, LocalMachine ]
default: LocalMachine
seealso:
- module: ansible.windows.win_certificate_store
author:
- Micah Hunsberger (@mhunsber)
'''
EXAMPLES = r'''
- name: Obtain information about a particular certificate in the computer's personal store
community.windows.win_certificate_info:
thumbprint: BD7AF104CF1872BDB518D95C9534EA941665FD27
register: mycert
# thumbprint can also be lower case
- name: Obtain information about a particular certificate in the computer's personal store
community.windows.win_certificate_info:
thumbprint: bd7af104cf1872bdb518d95c9534ea941665fd27
register: mycert
- name: Obtain information about all certificates in the root store
community.windows.win_certificate_info:
store_name: Root
register: ca
# Import a pfx and then get information on the certificates
- name: Import pfx certificate that is password protected
ansible.windows.win_certificate_store:
path: C:\Temp\cert.pfx
state: present
password: VeryStrongPasswordHere!
become: yes
become_method: runas
register: mycert
- name: Obtain information on each certificate that was touched
community.windows.win_certificate_info:
thumbprint: "{{ item }}"
register: mycert_stats
loop: "{{ mycert.thumbprints }}"
'''
RETURN = r'''
exists:
description:
- Whether any certificates were found in the store.
- When I(thumbprint) is specified, returns true only if the certificate mathing the thumbprint exists.
returned: success
type: bool
sample: true
certificates:
description:
- A list of information about certificates found in the store, sorted by thumbprint.
returned: success
type: list
elements: dict
contains:
archived:
description: Indicates that the certificate is archived.
type: bool
sample: false
dns_names:
description: Lists the registered dns names for the certificate.
type: list
elements: str
sample: [ '*.m.wikiquote.org', '*.wikipedia.org' ]
extensions:
description: The collection of the certificates extensions.
type: list
elements: dict
sample: [
{
"critical": false,
"field": "Subject Key Identifier",
"value": "88 27 17 09 a9 b6 18 60 8b ec eb ba f6 47 59 c5 52 54 a3 b7"
},
{
"critical": true,
"field": "Basic Constraints",
"value": "Subject Type=CA, Path Length Constraint=None"
},
{
"critical": false,
"field": "Authority Key Identifier",
"value": "KeyID=2b d0 69 47 94 76 09 fe f4 6b 8d 2e 40 a6 f7 47 4d 7f 08 5e"
},
{
"critical": false,
"field": "CRL Distribution Points",
"value": "[1]CRL Distribution Point: Distribution Point Name:Full Name:URL=http://crl.apple.com/root.crl"
},
{
"critical": true,
"field": "Key Usage",
"value": "Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86)"
},
{
"critical": false,
"field": null,
"value": "05 00"
}
]
friendly_name:
description: The associated alias for the certificate.
type: str
sample: Microsoft Root Authority
has_private_key:
description: Indicates that the certificate contains a private key.
type: bool
sample: false
intended_purposes:
description: lists the intended applications for the certificate.
returned: enhanced key usages extension exists.
type: list
sample: [ "Server Authentication" ]
is_ca:
description: Indicates that the certificate is a certificate authority (CA) certificate.
returned: basic constraints extension exists.
type: bool
sample: true
issued_by:
description: The certificate issuer's common name.
type: str
sample: Apple Root CA
issued_to:
description: The certificate's common name.
type: str
sample: Apple Worldwide Developer Relations Certification Authority
issuer:
description: The certificate issuer's distinguished name.
type: str
sample: 'CN=Apple Root CA, OU=Apple Certification Authority, O=Apple Inc., C=US'
key_usages:
description:
- Defines how the certificate key can be used.
- If this value is not defined, the key can be used for any purpose.
returned: key usages extension exists.
type: list
elements: str
sample: [ "CrlSign", "KeyCertSign", "DigitalSignature" ]
path_length_constraint:
description:
- The number of levels allowed in a certificates path.
- If this value is 0, the certificate does not have a restriction.
returned: basic constraints extension exists
type: int
sample: 0
public_key:
description: The base64 encoded public key of the certificate.
type: str
cert_data:
description: The base64 encoded data of the entire certificate.
type: str
serial_number:
description: The serial number of the certificate represented as a hexadecimal string
type: str
sample: 01DEBCC4396DA010
signature_algorithm:
description: The algorithm used to create the certificate's signature
type: str
sample: sha1RSA
ski:
description: The certificate's subject key identifier
returned: subject key identifier extension exists.
type: str
sample: 88271709A9B618608BECEBBAF64759C55254A3B7
subject:
description: The certificate's distinguished name.
type: str
sample: 'CN=Apple Worldwide Developer Relations Certification Authority, OU=Apple Worldwide Developer Relations, O=Apple Inc., C=US'
thumbprint:
description:
- The thumbprint as a hex string of the certificate.
- The return format will always be upper case.
type: str
sample: FF6797793A3CD798DC5B2ABEF56F73EDC9F83A64
valid_from:
description: The start date of the certificate represented in seconds since epoch.
type: float
sample: 1360255727
valid_from_iso8601:
description: The start date of the certificate represented as an iso8601 formatted date.
type: str
sample: '2017-12-15T08:39:32Z'
valid_to:
description: The expiry date of the certificate represented in seconds since epoch.
type: float
sample: 1675788527
valid_to_iso8601:
description: The expiry date of the certificate represented as an iso8601 formatted date.
type: str
sample: '2086-01-02T08:39:32Z'
version:
description: The x509 format version of the certificate
type: int
sample: 3
'''

View File

@@ -0,0 +1,54 @@
#!powershell
# Copyright: (c) 2019, RusoSova
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#AnsibleRequires -OSVersion 6.1
$spec = @{
options = @{
owner = @{ type = "str" }
organization = @{ type = "str" }
description = @{ type = "str" }
}
required_one_of = @(
, @('owner', 'organization', 'description')
)
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$owner = $module.Params.owner
$organization = $module.Params.organization
$description = $module.Params.description
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\"
#Change description
if ($description -or $description -eq "") {
$descriptionObject = Get-CimInstance -class "Win32_OperatingSystem"
if ($description -cne $descriptionObject.description) {
Set-CimInstance -InputObject $descriptionObject -Property @{"Description" = "$description" } -WhatIf:$module.CheckMode
$module.Result.changed = $true
}
}
#Change owner
if ($owner -or $owner -eq "") {
$curentOwner = (Get-ItemProperty -LiteralPath $regPath -Name RegisteredOwner).RegisteredOwner
if ($curentOwner -cne $owner) {
Set-ItemProperty -LiteralPath $regPath -Name "RegisteredOwner" -Value $owner -WhatIf:$module.CheckMode
$module.Result.changed = $true
}
}
#Change organization
if ($organization -or $organization -eq "") {
$curentOrganization = (Get-ItemProperty -LiteralPath $regPath -Name RegisteredOrganization).RegisteredOrganization
if ($curentOrganization -cne $organization) {
Set-ItemProperty -LiteralPath $regPath -Name "RegisteredOrganization" -Value $organization -WhatIf:$module.CheckMode
$module.Result.changed = $true
}
}
$module.ExitJson()

View File

@@ -0,0 +1,62 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, RusoSova
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: win_computer_description
short_description: Set windows description, owner and organization
description:
- This module sets Windows description that is shown under My Computer properties. Module also sets
Windows license owner and organization. License information can be viewed by running winver commad.
options:
description:
description:
- String value to apply to Windows descripton. Specify value of "" to clear the value.
required: false
type: str
organization:
description:
- String value of organization that the Windows is licensed to. Specify value of "" to clear the value.
required: false
type: str
owner:
description:
- String value of the persona that the Windows is licensed to. Specify value of "" to clear the value.
required: false
type: str
author:
- RusoSova (@RusoSova)
'''
EXAMPLES = r'''
- name: Set Windows description, owner and organization
community.windows.win_computer_description:
description: Best Box
owner: RusoSova
organization: MyOrg
register: result
- name: Set Windows description only
community.windows.win_computer_description:
description: This is my Windows machine
register: result
- name: Set organization and clear owner field
community.windows.win_computer_description:
owner: ''
organization: Black Mesa
- name: Clear organization, description and owner
community.windows.win_computer_description:
organization: ""
owner: ""
description: ""
register: result
'''
RETURN = r'''
#
'''

View File

@@ -0,0 +1,724 @@
#!powershell
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.AddType
$spec = @{
options = @{
alias = @{ type = "str" }
attributes = @{
type = "list"
elements = "dict"
options = @{
name = @{ type = "str"; required = $true }
data = @{ type = "str" }
data_format = @{ type = "str"; default = "text"; choices = @("base64", "text") }
}
}
comment = @{ type = "str" }
name = @{ type = "str"; required = $true }
persistence = @{ type = "str"; default = "local"; choices = @("enterprise", "local") }
secret = @{ type = "str"; no_log = $true }
secret_format = @{ type = "str"; default = "text"; choices = @("base64", "text") }
state = @{ type = "str"; default = "present"; choices = @("absent", "present") }
type = @{
type = "str"
required = $true
choices = @("domain_password", "domain_certificate", "generic_password", "generic_certificate")
}
update_secret = @{ type = "str"; default = "always"; choices = @("always", "on_create") }
username = @{ type = "str" }
}
required_if = @(
, @("state", "present", @("username"))
)
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$alias = $module.Params.alias
$attributes = $module.Params.attributes
$comment = $module.Params.comment
$name = $module.Params.name
$persistence = $module.Params.persistence
$secret = $module.Params.secret
$secret_format = $module.Params.secret_format
$state = $module.Params.state
$type = $module.Params.type
$update_secret = $module.Params.update_secret
$username = $module.Params.username
$module.Diff.before = ""
$module.Diff.after = ""
Add-CSharpType -AnsibleModule $module -References @'
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
namespace Ansible.CredentialManager
{
internal class NativeHelpers
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class CREDENTIAL
{
public CredentialFlags Flags;
public CredentialType Type;
[MarshalAs(UnmanagedType.LPWStr)] public string TargetName;
[MarshalAs(UnmanagedType.LPWStr)] public string Comment;
public FILETIME LastWritten;
public UInt32 CredentialBlobSize;
public IntPtr CredentialBlob;
public CredentialPersist Persist;
public UInt32 AttributeCount;
public IntPtr Attributes;
[MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias;
[MarshalAs(UnmanagedType.LPWStr)] public string UserName;
public static explicit operator Credential(CREDENTIAL v)
{
byte[] secret = new byte[(int)v.CredentialBlobSize];
if (v.CredentialBlob != IntPtr.Zero)
Marshal.Copy(v.CredentialBlob, secret, 0, secret.Length);
List<CredentialAttribute> attributes = new List<CredentialAttribute>();
if (v.AttributeCount > 0)
{
CREDENTIAL_ATTRIBUTE[] rawAttributes = new CREDENTIAL_ATTRIBUTE[v.AttributeCount];
Credential.PtrToStructureArray(rawAttributes, v.Attributes);
attributes = rawAttributes.Select(x => (CredentialAttribute)x).ToList();
}
string userName = v.UserName;
if (v.Type == CredentialType.DomainCertificate || v.Type == CredentialType.GenericCertificate)
userName = Credential.UnmarshalCertificateCredential(userName);
return new Credential
{
Type = v.Type,
TargetName = v.TargetName,
Comment = v.Comment,
LastWritten = (DateTimeOffset)v.LastWritten,
Secret = secret,
Persist = v.Persist,
Attributes = attributes,
TargetAlias = v.TargetAlias,
UserName = userName,
Loaded = true,
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct CREDENTIAL_ATTRIBUTE
{
[MarshalAs(UnmanagedType.LPWStr)] public string Keyword;
public UInt32 Flags; // Set to 0 and is reserved
public UInt32 ValueSize;
public IntPtr Value;
public static explicit operator CredentialAttribute(CREDENTIAL_ATTRIBUTE v)
{
byte[] value = new byte[v.ValueSize];
Marshal.Copy(v.Value, value, 0, (int)v.ValueSize);
return new CredentialAttribute
{
Keyword = v.Keyword,
Flags = v.Flags,
Value = value,
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct FILETIME
{
internal UInt32 dwLowDateTime;
internal UInt32 dwHighDateTime;
public static implicit operator long(FILETIME v) { return ((long)v.dwHighDateTime << 32) + v.dwLowDateTime; }
public static explicit operator DateTimeOffset(FILETIME v) { return DateTimeOffset.FromFileTime(v); }
public static explicit operator FILETIME(DateTimeOffset v)
{
return new FILETIME()
{
dwLowDateTime = (UInt32)v.ToFileTime(),
dwHighDateTime = ((UInt32)v.ToFileTime() >> 32),
};
}
}
[Flags]
public enum CredentialCreateFlags : uint
{
PreserveCredentialBlob = 1,
}
[Flags]
public enum CredentialFlags
{
None = 0,
PromptNow = 2,
UsernameTarget = 4,
}
public enum CredMarshalType : uint
{
CertCredential = 1,
UsernameTargetCredential,
BinaryBlobCredential,
UsernameForPackedCredential,
BinaryBlobForSystem,
}
}
internal class NativeMethods
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredDeleteW(
[MarshalAs(UnmanagedType.LPWStr)] string TargetName,
CredentialType Type,
UInt32 Flags);
[DllImport("advapi32.dll")]
public static extern void CredFree(
IntPtr Buffer);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredMarshalCredentialW(
NativeHelpers.CredMarshalType CredType,
SafeMemoryBuffer Credential,
out SafeCredentialBuffer MarshaledCredential);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredReadW(
[MarshalAs(UnmanagedType.LPWStr)] string TargetName,
CredentialType Type,
UInt32 Flags,
out SafeCredentialBuffer Credential);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredUnmarshalCredentialW(
[MarshalAs(UnmanagedType.LPWStr)] string MarshaledCredential,
out NativeHelpers.CredMarshalType CredType,
out SafeCredentialBuffer Credential);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredWriteW(
NativeHelpers.CREDENTIAL Credential,
NativeHelpers.CredentialCreateFlags Flags);
}
internal class SafeCredentialBuffer : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeCredentialBuffer() : base(true) { }
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
NativeMethods.CredFree(handle);
return true;
}
}
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeMemoryBuffer() : base(true) { }
public SafeMemoryBuffer(int cb) : base(true)
{
base.SetHandle(Marshal.AllocHGlobal(cb));
}
public SafeMemoryBuffer(IntPtr handle) : base(true)
{
base.SetHandle(handle);
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(handle);
return true;
}
}
public class Win32Exception : System.ComponentModel.Win32Exception
{
private string _exception_msg;
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
public Win32Exception(int errorCode, string message) : base(errorCode)
{
_exception_msg = String.Format("{0} - {1} (Win32 Error Code {2}: 0x{3})", message, base.Message, errorCode, errorCode.ToString("X8"));
}
public override string Message { get { return _exception_msg; } }
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
public enum CredentialPersist
{
Session = 1,
LocalMachine = 2,
Enterprise = 3,
}
public enum CredentialType
{
Generic = 1,
DomainPassword = 2,
DomainCertificate = 3,
DomainVisiblePassword = 4,
GenericCertificate = 5,
DomainExtended = 6,
Maximum = 7,
MaximumEx = 1007,
}
public class CredentialAttribute
{
public string Keyword;
public UInt32 Flags;
public byte[] Value;
}
public class Credential
{
public CredentialType Type;
public string TargetName;
public string Comment;
public DateTimeOffset LastWritten;
public byte[] Secret;
public CredentialPersist Persist;
public List<CredentialAttribute> Attributes = new List<CredentialAttribute>();
public string TargetAlias;
public string UserName;
// Used to track whether the credential has been loaded into the store or not
public bool Loaded { get; internal set; }
public void Delete()
{
if (!Loaded)
return;
if (!NativeMethods.CredDeleteW(TargetName, Type, 0))
throw new Win32Exception(String.Format("CredDeleteW({0}) failed", TargetName));
Loaded = false;
}
public void Write(bool preserveExisting)
{
string userName = UserName;
// Convert the certificate thumbprint to the string expected
if (Type == CredentialType.DomainCertificate || Type == CredentialType.GenericCertificate)
userName = Credential.MarshalCertificateCredential(userName);
NativeHelpers.CREDENTIAL credential = new NativeHelpers.CREDENTIAL
{
Flags = NativeHelpers.CredentialFlags.None,
Type = Type,
TargetName = TargetName,
Comment = Comment,
LastWritten = new NativeHelpers.FILETIME(),
CredentialBlobSize = (UInt32)(Secret == null ? 0 : Secret.Length),
CredentialBlob = IntPtr.Zero, // Must be allocated and freed outside of this to ensure no memory leaks
Persist = Persist,
AttributeCount = (UInt32)(Attributes.Count),
Attributes = IntPtr.Zero, // Attributes must be allocated and freed outside of this to ensure no memory leaks
TargetAlias = TargetAlias,
UserName = userName,
};
using (SafeMemoryBuffer credentialBlob = new SafeMemoryBuffer((int)credential.CredentialBlobSize))
{
if (Secret != null)
Marshal.Copy(Secret, 0, credentialBlob.DangerousGetHandle(), Secret.Length);
credential.CredentialBlob = credentialBlob.DangerousGetHandle();
// Store the CREDENTIAL_ATTRIBUTE value in a safe memory buffer and make sure we dispose in all cases
List<SafeMemoryBuffer> attributeBuffers = new List<SafeMemoryBuffer>();
try
{
int attributeLength = Attributes.Sum(a => Marshal.SizeOf(typeof(NativeHelpers.CREDENTIAL_ATTRIBUTE)));
byte[] attributeBytes = new byte[attributeLength];
int offset = 0;
foreach (CredentialAttribute attribute in Attributes)
{
SafeMemoryBuffer attributeBuffer = new SafeMemoryBuffer(attribute.Value.Length);
attributeBuffers.Add(attributeBuffer);
if (attribute.Value != null)
Marshal.Copy(attribute.Value, 0, attributeBuffer.DangerousGetHandle(), attribute.Value.Length);
NativeHelpers.CREDENTIAL_ATTRIBUTE credentialAttribute = new NativeHelpers.CREDENTIAL_ATTRIBUTE
{
Keyword = attribute.Keyword,
Flags = attribute.Flags,
ValueSize = (UInt32)(attribute.Value == null ? 0 : attribute.Value.Length),
Value = attributeBuffer.DangerousGetHandle(),
};
int attributeStructLength = Marshal.SizeOf(typeof(NativeHelpers.CREDENTIAL_ATTRIBUTE));
byte[] attrBytes = new byte[attributeStructLength];
using (SafeMemoryBuffer tempBuffer = new SafeMemoryBuffer(attributeStructLength))
{
Marshal.StructureToPtr(credentialAttribute, tempBuffer.DangerousGetHandle(), false);
Marshal.Copy(tempBuffer.DangerousGetHandle(), attrBytes, 0, attributeStructLength);
}
Buffer.BlockCopy(attrBytes, 0, attributeBytes, offset, attributeStructLength);
offset += attributeStructLength;
}
using (SafeMemoryBuffer attributes = new SafeMemoryBuffer(attributeBytes.Length))
{
if (attributeBytes.Length != 0)
{
Marshal.Copy(attributeBytes, 0, attributes.DangerousGetHandle(), attributeBytes.Length);
credential.Attributes = attributes.DangerousGetHandle();
}
NativeHelpers.CredentialCreateFlags createFlags = 0;
if (preserveExisting)
createFlags |= NativeHelpers.CredentialCreateFlags.PreserveCredentialBlob;
if (!NativeMethods.CredWriteW(credential, createFlags))
throw new Win32Exception(String.Format("CredWriteW({0}) failed", TargetName));
}
}
finally
{
foreach (SafeMemoryBuffer attributeBuffer in attributeBuffers)
attributeBuffer.Dispose();
}
}
Loaded = true;
}
public static Credential GetCredential(string target, CredentialType type)
{
SafeCredentialBuffer buffer;
if (!NativeMethods.CredReadW(target, type, 0, out buffer))
{
int lastErr = Marshal.GetLastWin32Error();
// Not running with Become so cannot manage the user's credentials
if (lastErr == 0x00000520) // ERROR_NO_SUCH_LOGON_SESSION
throw new InvalidOperationException("Failed to access the user's credential store, run the module with become");
else if (lastErr == 0x00000490) // ERROR_NOT_FOUND
return null;
throw new Win32Exception(lastErr, "CredEnumerateW() failed");
}
using (buffer)
{
NativeHelpers.CREDENTIAL credential = (NativeHelpers.CREDENTIAL)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.CREDENTIAL));
return (Credential)credential;
}
}
public static string MarshalCertificateCredential(string thumbprint)
{
// CredWriteW requires the UserName field to be the value of CredMarshalCredentialW() when writting a
// certificate auth. This converts the UserName property to the format required.
// While CERT_CREDENTIAL_INFO is the correct structure, we manually marshal the data in order to
// support different cert hash lengths in the future.
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_cert_credential_info
int hexLength = thumbprint.Length;
byte[] credInfo = new byte[sizeof(UInt32) + (hexLength / 2)];
// First field is cbSize which is a UInt32 value denoting the size of the total structure
Array.Copy(BitConverter.GetBytes((UInt32)credInfo.Length), credInfo, sizeof(UInt32));
// Now copy the byte representation of the thumbprint to the rest of the struct bytes
for (int i = 0; i < hexLength; i += 2)
credInfo[sizeof(UInt32) + (i / 2)] = Convert.ToByte(thumbprint.Substring(i, 2), 16);
IntPtr pCredInfo = Marshal.AllocHGlobal(credInfo.Length);
Marshal.Copy(credInfo, 0, pCredInfo, credInfo.Length);
SafeMemoryBuffer pCredential = new SafeMemoryBuffer(pCredInfo);
NativeHelpers.CredMarshalType marshalType = NativeHelpers.CredMarshalType.CertCredential;
using (pCredential)
{
SafeCredentialBuffer marshaledCredential;
if (!NativeMethods.CredMarshalCredentialW(marshalType, pCredential, out marshaledCredential))
throw new Win32Exception("CredMarshalCredentialW() failed");
using (marshaledCredential)
return Marshal.PtrToStringUni(marshaledCredential.DangerousGetHandle());
}
}
public static string UnmarshalCertificateCredential(string value)
{
NativeHelpers.CredMarshalType credType;
SafeCredentialBuffer pCredInfo;
if (!NativeMethods.CredUnmarshalCredentialW(value, out credType, out pCredInfo))
throw new Win32Exception("CredUnmarshalCredentialW() failed");
using (pCredInfo)
{
if (credType != NativeHelpers.CredMarshalType.CertCredential)
throw new InvalidOperationException(String.Format("Expected unmarshalled cred type of CertCredential, received {0}", credType));
byte[] structSizeBytes = new byte[sizeof(UInt32)];
Marshal.Copy(pCredInfo.DangerousGetHandle(), structSizeBytes, 0, sizeof(UInt32));
UInt32 structSize = BitConverter.ToUInt32(structSizeBytes, 0);
byte[] certInfoBytes = new byte[structSize];
Marshal.Copy(pCredInfo.DangerousGetHandle(), certInfoBytes, 0, certInfoBytes.Length);
StringBuilder hex = new StringBuilder((certInfoBytes.Length - sizeof(UInt32)) * 2);
for (int i = 4; i < certInfoBytes.Length; i++)
hex.AppendFormat("{0:x2}", certInfoBytes[i]);
return hex.ToString().ToUpperInvariant();
}
}
internal static void PtrToStructureArray<T>(T[] array, IntPtr ptr)
{
IntPtr ptrOffset = ptr;
for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T))))
array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T));
}
}
}
'@
Function ConvertTo-CredentialAttribute {
param($Attributes)
$converted_attributes = [System.Collections.Generic.List`1[Ansible.CredentialManager.CredentialAttribute]]@()
foreach ($attribute in $Attributes) {
$new_attribute = New-Object -TypeName Ansible.CredentialManager.CredentialAttribute
$new_attribute.Keyword = $attribute.name
if ($null -ne $attribute.data) {
if ($attribute.data_format -eq "base64") {
$new_attribute.Value = [System.Convert]::FromBase64String($attribute.data)
}
else {
$new_attribute.Value = [System.Text.Encoding]::UTF8.GetBytes($attribute.data)
}
}
$converted_attributes.Add($new_attribute) > $null
}
return , $converted_attributes
}
Function Get-DiffInfo {
param($AnsibleCredential)
$diff = @{
alias = $AnsibleCredential.TargetAlias
attributes = [System.Collections.ArrayList]@()
comment = $AnsibleCredential.Comment
name = $AnsibleCredential.TargetName
persistence = $AnsibleCredential.Persist.ToString()
type = $AnsibleCredential.Type.ToString()
username = $AnsibleCredential.UserName
}
foreach ($attribute in $AnsibleCredential.Attributes) {
$attribute_info = @{
name = $attribute.Keyword
data = $null
}
if ($null -ne $attribute.Value) {
$attribute_info.data = [System.Convert]::ToBase64String($attribute.Value)
}
$diff.attributes.Add($attribute_info) > $null
}
return , $diff
}
# If the username is a certificate thumbprint, verify it's a valid cert in the CurrentUser/Personal store
if ($null -ne $username -and $type -in @("domain_certificate", "generic_certificate")) {
# Ensure the thumbprint is upper case with no spaces or hyphens
$username = $username.ToUpperInvariant().Replace(" ", "").Replace("-", "")
$certificate = Get-Item -LiteralPath Cert:\CurrentUser\My\$username -ErrorAction SilentlyContinue
if ($null -eq $certificate) {
$module.FailJson("Failed to find certificate with the thumbprint $username in the CurrentUser\My store")
}
}
# Convert the input secret to a byte array
if ($null -ne $secret) {
if ($secret_format -eq "base64") {
$secret = [System.Convert]::FromBase64String($secret)
}
else {
$secret = [System.Text.Encoding]::Unicode.GetBytes($secret)
}
}
$persistence = switch ($persistence) {
"local" { [Ansible.CredentialManager.CredentialPersist]::LocalMachine }
"enterprise" { [Ansible.CredentialManager.CredentialPersist]::Enterprise }
}
$type = switch ($type) {
"domain_password" { [Ansible.CredentialManager.CredentialType]::DomainPassword }
"domain_certificate" { [Ansible.CredentialManager.CredentialType]::DomainCertificate }
"generic_password" { [Ansible.CredentialManager.CredentialType]::Generic }
"generic_certificate" { [Ansible.CredentialManager.CredentialType]::GenericCertificate }
}
$existing_credential = [Ansible.CredentialManager.Credential]::GetCredential($name, $type)
if ($null -ne $existing_credential) {
$module.Diff.before = Get-DiffInfo -AnsibleCredential $existing_credential
}
if ($state -eq "absent") {
if ($null -ne $existing_credential) {
if (-not $module.CheckMode) {
$existing_credential.Delete()
}
$module.Result.changed = $true
}
}
else {
if ($null -eq $existing_credential) {
$new_credential = New-Object -TypeName Ansible.CredentialManager.Credential
$new_credential.Type = $type
$new_credential.TargetName = $name
$new_credential.Comment = if ($comment) { $comment } else { [NullString]::Value }
$new_credential.Secret = $secret
$new_credential.Persist = $persistence
$new_credential.TargetAlias = if ($alias) { $alias } else { [NullString]::Value }
$new_credential.UserName = $username
if ($null -ne $attributes) {
$new_credential.Attributes = ConvertTo-CredentialAttribute -Attributes $attributes
}
if (-not $module.CheckMode) {
$new_credential.Write($false)
}
$module.Result.changed = $true
}
else {
$changed = $false
$preserve_blob = $false
# make sure we do case comparison for the comment
if ($existing_credential.Comment -cne $comment) {
$existing_credential.Comment = $comment
$changed = $true
}
if ($existing_credential.Persist -ne $persistence) {
$existing_credential.Persist = $persistence
$changed = $true
}
if ($existing_credential.TargetAlias -ne $alias) {
$existing_credential.TargetAlias = $alias
$changed = $true
}
if ($existing_credential.UserName -ne $username) {
$existing_credential.UserName = $username
$changed = $true
}
if ($null -ne $attributes) {
$attribute_changed = $false
$new_attributes = ConvertTo-CredentialAttribute -Attributes $attributes
if ($new_attributes.Count -ne $existing_credential.Attributes.Count) {
$attribute_changed = $true
}
else {
for ($i = 0; $i -lt $new_attributes.Count; $i++) {
$new_keyword = $new_attributes[$i].Keyword
$new_value = $new_attributes[$i].Value
if ($null -eq $new_value) {
$new_value = ""
}
else {
$new_value = [System.Convert]::ToBase64String($new_value)
}
$existing_keyword = $existing_credential.Attributes[$i].Keyword
$existing_value = $existing_credential.Attributes[$i].Value
if ($null -eq $existing_value) {
$existing_value = ""
}
else {
$existing_value = [System.Convert]::ToBase64String($existing_value)
}
if (($new_keyword -cne $existing_keyword) -or ($new_value -ne $existing_value)) {
$attribute_changed = $true
break
}
}
}
if ($attribute_changed) {
$existing_credential.Attributes = $new_attributes
$changed = $true
}
}
if ($null -eq $secret) {
# If we haven't explicitly set a secret, tell Windows to preserve the existing blob
$preserve_blob = $true
$existing_credential.Secret = $null
}
elseif ($update_secret -eq "always") {
# We should only set the password if we can't read the existing one or it doesn't match our secret
if ($existing_credential.Secret.Length -eq 0) {
# We cannot read the secret so don't know if its the configured secret
$existing_credential.Secret = $secret
$changed = $true
}
else {
# We can read the secret so compare with our input
$input_secret_b64 = [System.Convert]::ToBase64String($secret)
$actual_secret_b64 = [System.Convert]::ToBase64String($existing_credential.Secret)
if ($input_secret_b64 -ne $actual_secret_b64) {
$existing_credential.Secret = $secret
$changed = $true
}
}
}
if ($changed -and -not $module.CheckMode) {
$existing_credential.Write($preserve_blob)
}
$module.Result.changed = $changed
}
if ($module.CheckMode) {
# We cannot reliably get the credential in check mode, set it based on the input
$module.Diff.after = @{
alias = $alias
attributes = $attributes
comment = $comment
name = $name
persistence = $persistence.ToString()
type = $type.ToString()
username = $username
}
}
else {
# Get a new copy of the credential and use that to set the after diff
$new_credential = [Ansible.CredentialManager.Credential]::GetCredential($name, $type)
$module.Diff.after = Get-DiffInfo -AnsibleCredential $new_credential
}
}
$module.ExitJson()

View File

@@ -0,0 +1,206 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: win_credential
short_description: Manages Windows Credentials in the Credential Manager
description:
- Used to create and remove Windows Credentials in the Credential Manager.
- This module can manage both standard username/password credentials as well as
certificate credentials.
options:
alias:
description:
- Adds an alias for the credential.
- Typically this is the NetBIOS name of a host if I(name) is set to the DNS
name.
type: str
attributes:
description:
- A list of dicts that set application specific attributes for a
credential.
- When set, existing attributes will be compared to the list as a whole,
any differences means all attributes will be replaced.
type: list
elements: dict
suboptions:
name:
description:
- The key for the attribute.
- This is not a unique identifier as multiple attributes can have the
same key.
type: str
required: true
data:
description:
- The value for the attribute.
type: str
data_format:
description:
- Controls the input type for I(data).
- If C(text), I(data) is a text string that is UTF-16LE encoded to
bytes.
- If C(base64), I(data) is a base64 string that is base64 decoded to
bytes.
type: str
choices: [ base64, text ]
default: text
comment:
description:
- A user defined comment for the credential.
type: str
name:
description:
- The target that identifies the server or servers that the credential is
to be used for.
- If the value can be a NetBIOS name, DNS server name, DNS host name suffix
with a wildcard character (C(*)), a NetBIOS of DNS domain name that
contains a wildcard character sequence, or an asterisk.
- See C(TargetName) in U(https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credentiala)
for more details on what this value can be.
- This is used with I(type) to produce a unique credential.
type: str
required: true
persistence:
description:
- Defines the persistence of the credential.
- If C(local), the credential will persist for all logons of the same user
on the same host.
- C(enterprise) is the same as C(local) but the credential is visible to
the same domain user when running on other hosts and not just localhost.
type: str
choices: [ enterprise, local ]
default: local
secret:
description:
- The secret for the credential.
- When omitted, then no secret is used for the credential if a new
credentials is created.
- When I(type) is a password type, this is the password for I(username).
- When I(type) is a certificate type, this is the pin for the certificate.
type: str
secret_format:
description:
- Controls the input type for I(secret).
- If C(text), I(secret) is a text string that is UTF-16LE encoded to bytes.
- If C(base64), I(secret) is a base64 string that is base64 decoded to
bytes.
type: str
choices: [ base64, text ]
default: text
state:
description:
- When C(absent), the credential specified by I(name) and I(type) is
removed.
- When C(present), the credential specified by I(name) and I(type) is
removed.
type: str
choices: [ absent, present ]
default: present
type:
description:
- The type of credential to store.
- This is used with I(name) to produce a unique credential.
- When the type is a C(domain) type, the credential is used by Microsoft
authentication packages like Negotiate.
- When the type is a C(generic) type, the credential is not used by any
particular authentication package.
- It is recommended to use a C(domain) type as only authentication
providers can access the secret.
type: str
required: true
choices: [ domain_certificate, domain_password, generic_certificate, generic_password ]
update_secret:
description:
- When C(always), the secret will always be updated if they differ.
- When C(on_create), the secret will only be checked/updated when it is
first created.
- If the secret cannot be retrieved and this is set to C(always), the
module will always result in a change.
type: str
choices: [ always, on_create ]
default: always
username:
description:
- When I(type) is a password type, then this is the username to store for
the credential.
- When I(type) is a credential type, then this is the thumbprint as a hex
string of the certificate to use.
- When C(type=domain_password), this should be in the form of a Netlogon
(DOMAIN\Username) or a UPN (username@DOMAIN).
- If using a certificate thumbprint, the certificate must exist in the
C(CurrentUser\My) certificate store for the executing user.
type: str
notes:
- This module requires to be run with C(become) so it can access the
user's credential store.
- There can only be one credential per host and type. if a second credential is
defined that uses the same host and type, then the original credential is
overwritten.
seealso:
- module: ansible.windows.win_user_right
- module: ansible.windows.win_whoami
author:
- Jordan Borean (@jborean93)
'''
EXAMPLES = r'''
- name: Create a local only credential
community.windows.win_credential:
name: server.domain.com
type: domain_password
username: DOMAIN\username
secret: Password01
state: present
- name: Remove a credential
community.windows.win_credential:
name: server.domain.com
type: domain_password
state: absent
- name: Create a credential with full values
community.windows.win_credential:
name: server.domain.com
type: domain_password
alias: server
username: username@DOMAIN.COM
secret: Password01
comment: Credential for server.domain.com
persistence: enterprise
attributes:
- name: Source
data: Ansible
- name: Unique Identifier
data: Y3VzdG9tIGF0dHJpYnV0ZQ==
data_format: base64
- name: Create a certificate credential
community.windows.win_credential:
name: '*.domain.com'
type: domain_certificate
username: 0074CC4F200D27DC3877C24A92BA8EA21E6C7AF4
state: present
- name: Create a generic credential
community.windows.win_credential:
name: smbhost
type: generic_password
username: smbuser
secret: smbuser
state: present
- name: Remove a generic credential
community.windows.win_credential:
name: smbhost
type: generic_password
state: absent
'''
RETURN = r'''
#
'''

Some files were not shown because too many files have changed in this diff Show More