Skip to content

Commit a087e1a

Browse files
Merge pull request #291 from RandomNoun7/tickets/master/MODULES-8606-bolt-accounts-tasks
(MODULES-8606) Add SQL Logins Tasks
2 parents 54ea382 + 9d43270 commit a087e1a

File tree

5 files changed

+675
-0
lines changed

5 files changed

+675
-0
lines changed

README.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
* [Manage the user's permissions](#manage-the-above-users-permissions)
1616
* [Run custom TSQL statements](#run-custom-tsql-statements)
1717
5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
18+
* [Types](#types)
19+
* [Defined Types](#defined-types)
20+
* [Bolt Tasks](#bolt-tasks)
1821
6. [Limitations - OS compatibility, etc.](#limitations)
1922
7. [Development - Guide for contributing to the module](#development)
2023

@@ -1056,6 +1059,108 @@ Default: `false`.
10561059
> * [Reconfigure](http://msdn.microsoft.com/en-us/library/ms176069.aspx)
10571060
> * [Server Configuration Options](http://msdn.microsoft.com/en-us/library/ms189631.aspx)
10581061
1062+
### Bolt Tasks
1063+
1064+
#### sqlserver::get_sql_logins
1065+
1066+
This task will retrieve information about a login, or a set of logins, from the sql instances running on a given node.
1067+
With no parameters specified it will return summary level information about all logins configured for all sql instances running on a given node or node set.
1068+
Use the `detailed` parameter to return more detailed information including the SID's and the name of the instance a login was retrieved from.
1069+
1070+
##### parameters
1071+
1072+
* **instance_name**
1073+
1074+
The name of the instance to query for logins. By default, leave blank for all instances running on a node.
1075+
Pass the values `.`, `MSSQLSERVER`, or the node name to query just the default instance.
1076+
Named instances can be referred to by either the short name of the instance, or by `<COMPUTERNAME>\<INSTANCE_NAME>`.
1077+
This is an optional parameter which will accept a single string or array of strings as input.
1078+
1079+
* **login_name**
1080+
1081+
The name of a particular login to search for, or the search pattern for a set of logins.
1082+
If no value is passed to this variable then all logins are returned.
1083+
By default any values passed to this parameter are treated like a search string.
1084+
Searches are done using the PowerShell `-match` operator.
1085+
For example, if the string `sql` is passed to this parameter, logins such as `NT SERVICE\SQLWriter`
1086+
and `##MS_PolicyTsqlExecutionLogin##` will be returned in the result set.
1087+
If the `exact_match` parameter is set to true, then only exact matches are accepted and neither of those logins would have been returned.
1088+
This is an optional parameter which will accept a single string or array of strings as input.
1089+
1090+
* **exact_match**
1091+
1092+
Set this to true to change the behavior of the `login_name` parameter so that only logins exactly matching one of the provided login_names is returned in the result set.
1093+
This is an optional parameter which will accept either `true` or `false`. It's default value is false.
1094+
1095+
* **detailed**
1096+
1097+
This parameter causes the task to return a more detailed level of information for each login.
1098+
By default the list of properties returned about each login is: `Name`,`isDisabled`,`isLocked`,`IsPasswordExpired`,`CreateDate`,`DateLastModified`.
1099+
Setting this parameter to true adds in the following properties: `DefaultDatabase`,`DenyWindowsLogin`,`HasAccess`,`ID`,`IsSystemObject`,`Language`,`LanguageAlias`,`,LoginType`,`MustChangePassword`,`PasswordExpirationEnabled`,`PasswordHashAlgorithm`,`PasswordPolicyEnforced`,`SQLSID`,`ADSid`,`WindowsLoginAccessType`,`UserData`,`State`,`IsDesignMode`,`InstanceName`
1100+
1101+
> **A note about `SQLSID` and `ADSID`.**
1102+
>
1103+
> The `SQLSID` property is this module's property name for the binary representation of a SID that SQLServer keeps internally in tables like `sys.server_principals`.
1104+
> It will look like `0x01` for accounts like `sa`, or something longer like `0x0106000000000009010000005FB6DAC7F7DB546D706711B128B5063888B01770` for other accounts.
1105+
> This `sid` does not look like a normal `sid` you might see outside of SQLServer, but it is returned as part of the detailed information to make it easier to correlate the logins returned by this module and query results from SQLServer.
1106+
> The `ADSID` property is a more normal looking `sid` you might get from PowerShell Active Directory query tools.
1107+
> It is a direct translation of that `SQLSID` into the Microsoft string `sid` form and will look something like `S-1-5-80-1402415987-66678372-3059512406-1823130485-2345841878`.
1108+
> This translation is done to make it easier to correlate SQLServer logins with AD users and detect when something like a user has been disconnected from its real AD SID.
1109+
> If the detailed information for a login does not contain a value for the AD SID property, it means that the login is internal to SQLServer and does not have a valid AD format `sid`.
1110+
1111+
#### sqlserver::set_sql_logins
1112+
1113+
##### parameters
1114+
1115+
* **instance_name**
1116+
1117+
The name of the instance to find the login you are setting properties on.
1118+
By default this parameter will use the default instance only.
1119+
Named instances can be referred to by either the short name of the instance, or by `<COMPUTERNAME>\<INSTANCE_NAME>`.
1120+
Specifying an instance name will access only that instance. To affect a login on more than one instance,
1121+
specify all of the required instance names as an array of values.
1122+
Pass the values `.`, `MSSQLSERVER`, or the node name to query only the default instance.
1123+
This is an optional parameter which will accept a single string or array of strings as input.
1124+
1125+
* **login_name**
1126+
1127+
The name of a particular login to to set properties on.
1128+
By default this parameter expects an exact match. To use pattern matching see the `fuzzy_match` parameter.
1129+
This is an optional parameter which will accept a single string or array of strings as input.
1130+
1131+
* **fuzzy_match**
1132+
1133+
Modifies the behavior of the `login_name` parameter so that the value given is a login name pattern to match.
1134+
Searches are done using the PowerShell `-match` operator.
1135+
For example, if the string `sql` is passed to the `login_name` parameter, while `fuzzy_match` is set to true, logins such as `NT SERVICE\SQLWriter`
1136+
and `##MS_PolicyTsqlExecutionLogin##` will be returned in the result set.
1137+
This is an
1138+
optional parameter which will accept either `true` or `false`. Its default value is false.
1139+
1140+
* **enbabled**
1141+
1142+
Set this parameter to true to enable a login and set to false to disable it.
1143+
This is an optional boolean parameter. The return value will be an element in the json return
1144+
specifying that the new value for the `isDisabled` property of the login object will be either `true` or `false`.
1145+
1146+
* **password**
1147+
1148+
Provide a value specifying the new password to use for a login.
1149+
Please note that there are possible string parsing issues when using this parameter.
1150+
For instance attempting to set a password `'pa$ssword#"'` may not parse correctly.
1151+
To get the double quote to end up in the password correctly you will need to triple quote escape the double quote,
1152+
like so `'pa$ssword#"""'`.
1153+
To ensure you password is interpreted and set correctly you may want to try to echo the password out using `bolt command run` first
1154+
to ensure the characters end up on the target node correctly like so, `bolt command run 'Write-Host ''This is """awesome""".'''`
1155+
1156+
##### noop
1157+
1158+
This task supports the `--noop` flag. The task will return json values indicating the
1159+
actions it would have taken and the logins it would have affected without actually taking any action.
1160+
It will not inspect and return to you what the state of a property was before taking the action.
1161+
Use this parameter especially when using the `fuzzy_match` parameter to ensure you are affecting
1162+
only the logins you intend to.
1163+
10591164
### Microsoft SQL Server terms
10601165

10611166
Terminology differs somewhat between various database systems; please refer to this list of terms for clarification.

tasks/get_sql_logins.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"puppet_task_version": 1,
3+
"supports_noop": false,
4+
"input_method": "powershell",
5+
"description": "Retrieve information about the logins configured for a SQL Server instance.",
6+
"parameters": {
7+
"instance_name": {
8+
"description": "The name of the SQL Instance running on the machine to connect to. Leave blank for the default instance of MSSQLSERVER",
9+
"type": "Optional[Variant[Array[String], String]]"
10+
},
11+
"login_name": {
12+
"description": "The name of a particular login to search for. You can use partial names and any pattern that will work with the PowerShell '-match' operator.",
13+
"type": "Optional[Variant[Array[String], String]]"
14+
},
15+
"exact_match": {
16+
"description": "If set to true it will force names passed to the LoginName parameter to be an exact match to a SQL Login to pass the filter.",
17+
"type": "Optional[Boolean]"
18+
},
19+
"detailed": {
20+
"description": "Return more detailed information from the server instead of the default summary information",
21+
"type": "Optional[Boolean]"
22+
}
23+
}
24+
}

tasks/get_sql_logins.ps1

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
[CmdletBinding()]
2+
param (
3+
# The name of the SQL Instance running on the node.
4+
[String[]]$instance_name,
5+
# The name of the SQL Login to get information about.
6+
[string[]]$login_name,
7+
# If true this will force the name to match a login exactly to be returned.
8+
[switch]$exact_match,
9+
# Return more detailed information about logins including SID's.
10+
[switch]$detailed
11+
)
12+
13+
$error = @{
14+
_error = @{
15+
msg = ''
16+
kind = 'puppetlabs.task/task-error'
17+
details = @{
18+
detailedInfo = ''
19+
exitcode = 1
20+
}
21+
}
22+
}
23+
24+
function Select-LoginName {
25+
param(
26+
[PSObject]$login,
27+
[string[]]$namesToMatch,
28+
[switch]$exact_match
29+
)
30+
31+
32+
# This function takes a single SQLServer login object and compares it against
33+
# the list of names passed into the -login_name parameter of the script to
34+
# determine if this is a login the user is interested in seeing. If it does
35+
# not pass the filter represented by that parameter the login is discarded.
36+
37+
foreach ($paramLogin in $namesToMatch) {
38+
if ($exact_match) {
39+
if ($paramLogin -eq $login.name) {
40+
Write-Output $login
41+
}
42+
}
43+
else {
44+
# Match is a regex operator, and it doesn't like the '\' in domain names.
45+
if ($login.name -match [regex]::escape($paramLogin)) {
46+
Write-Output $login
47+
}
48+
}
49+
}
50+
}
51+
52+
function Get-SQLInstances {
53+
param(
54+
[string[]]$instance_name
55+
)
56+
57+
$instancesHolder = New-Object System.Collections.Generic.List[System.Object]
58+
$stringsToReturn = New-Object System.Collections.Generic.List[System.Object]
59+
60+
# The default instance is referred to in its service name as MSSQLSERVER. This
61+
# leads many SQLSERVER people to refer to it as such. They will also connect
62+
# to it using just a '.'. None of these are its real name. Its real instance
63+
# name is just the machine name. A named instances real name is the machine
64+
# name a, '\', and the instance name. This little foreach ensures that we are
65+
# referring to these instances by their real names so that proper filtering
66+
# can be done.
67+
68+
foreach ($name in $instance_name) {
69+
switch ($name) {
70+
{($_ -eq 'MSSQLSERVER') -or ($_ -eq '.')} { [void]$instancesHolder.add($env:COMPUTERNAME) }
71+
{$_ -eq $env:COMPUTERNAME} { [void]$instancesHolder.add($_) }
72+
{$_ -notmatch '\\'} { [void]$instancesHolder.add("$env:COMPUTERNAME\$_") }
73+
default { [void]$instancesHolder.add($name) }
74+
}
75+
}
76+
77+
$instanceStrings = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server').InstalledInstances
78+
79+
# The registry key does not return the real instance names. Again we must
80+
# normalize these names into their real names so that comparisons can be done
81+
# properly.
82+
83+
foreach ($string in $instanceStrings) {
84+
switch ($string) {
85+
'MSSQLSERVER' { $string = $env:COMPUTERNAME }
86+
Default {$string = "$env:COMPUTERNAME\$string"}
87+
}
88+
89+
if ((-not [string]::IsNullOrEmpty($instancesHolder))-and(-not [string]::IsNullOrWhiteSpace($instancesHolder))) {
90+
foreach ($instance in $instancesHolder) {
91+
if ($instance -eq $string) {
92+
[void]$stringsToReturn.add($string)
93+
}
94+
}
95+
}
96+
else {
97+
[void]$stringsToReturn.add($string)
98+
}
99+
}
100+
101+
if($stringsToReturn.count -gt 0){
102+
Write-Output $stringsToReturn
103+
} else {
104+
throw "No instances were found by the name(s) $instance_name"
105+
}
106+
}
107+
108+
function Get-ServerObject {
109+
param(
110+
[string]$instance
111+
)
112+
113+
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo")
114+
115+
Write-Output (New-Object Microsoft.SqlServer.Management.Smo.Server -ArgumentList $instance)
116+
117+
}
118+
119+
$return = @{}
120+
121+
#Get SQL Instances
122+
123+
try {
124+
$SQLInstances = Get-SQLInstances -instance_name $instance_name
125+
}
126+
catch {
127+
$error._error.msg = 'Cannot detect SQL instance names.'
128+
$error._error.details.detailedInfo = $_
129+
return $error | ConvertTo-JSON
130+
}
131+
132+
# Unfiltered Logins from all instances.
133+
$rawLogins = New-Object System.Collections.Generic.List[System.Object]
134+
135+
foreach ($instance in $SQLInstances) {
136+
try {
137+
$sqlServer = Get-ServerObject -instance $instance
138+
}
139+
catch {
140+
$error._error.msg = "Cannot connect to SQL Instance: $instance"
141+
$error._error.details.detailedInfo = $_
142+
return $error | ConvertTo-JSON
143+
}
144+
145+
foreach ($item in $sqlServer.logins) {
146+
# The login object doesn't return information about which instance it
147+
# came from. This could be a problem on a machine running more than
148+
# one instance. We'll add a property here to make sure we don't lose
149+
# track of this information.
150+
151+
Add-Member -InputObject $item -MemberType NoteProperty -Name 'InstanceName' -Value $instance
152+
153+
[void]$rawLogins.add($item)
154+
}
155+
}
156+
157+
# Filtered logins based on the filter passed into the $login_name parameter
158+
$logins = New-Object System.Collections.Generic.List[System.Object]
159+
160+
foreach ($login in $rawLogins) {
161+
if ($MyInvocation.BoundParameters.ContainsKey('login_name')) {
162+
[void]$logins.add((Select-LoginName -login $login -namesToMatch $login_name -exact_match:$exact_match))
163+
}
164+
else {
165+
[void]$logins.add($login)
166+
}
167+
}
168+
169+
if ($detailed) {
170+
171+
# The SID property of the Login object contains an array of integers. This
172+
# is not how SQL Server represents the login int he sys.server_principals
173+
# table. The array of integers needs to be converted one by one into their
174+
# Hex representation, and then joined together. This will match the value
175+
# in the server_principals table and allow someone executing this script
176+
# to correlate users properly.
177+
178+
$sidFunction = {
179+
$finalSid = '0x'
180+
foreach ($segment in $_.sid) {
181+
$finalSid += ("{0:x2}" -f $segment).ToUpper()
182+
}
183+
$finalSid
184+
}
185+
186+
# The SID column of sys.server_principals stores the binary representation
187+
# of an account SID. This is not a SID that can easily be correlated to an
188+
# Active Directory style SID that you can find in most other tools. This
189+
# function converts SQLServer's binary sid into a SID string that people
190+
# are more familiar with.
191+
192+
$winSidFunction = {
193+
(New-Object System.Security.Principal.SecurityIdentifier($_.sid, 0)).toString()
194+
}
195+
196+
$properties = @(
197+
'Name'
198+
@{N = 'CreateDate'; E = {"$($_.CreateDate)"}}
199+
@{N = 'DateLastModified'; E = {"$($_.DateLastModified)"}}
200+
'InstanceName'
201+
'DefaultDatabase'
202+
'DenyWindowsLogin'
203+
'HasAccess'
204+
'ID'
205+
'IsDisabled'
206+
'IsLocked'
207+
'IsPasswordExpired'
208+
'IsSystemObject'
209+
'Language'
210+
'LanguageAlias'
211+
'LoginType'
212+
'MustChangePassword'
213+
'PasswordExpirationEnabled'
214+
'PasswordHashAlgorithm'
215+
'PasswordPolicyEnforced'
216+
@{N = 'SQLSID'; E = $sidFunction}
217+
@{N = 'ADSid'; E = $winSidFunction}
218+
'WindowsLoginAccessType'
219+
'UserData'
220+
'State'
221+
'IsDesignMode'
222+
)
223+
224+
$return.logins = $logins | Select-Object $properties
225+
$return | ConvertTo-JSON -Depth 99
226+
}
227+
else {
228+
$properties = @(
229+
'Name'
230+
'isDisabled'
231+
'isLocked'
232+
'IsPasswordExpired'
233+
@{N = 'CreateDate'; E = {"$($_.CreateDate)"}}
234+
@{N = 'DateLastModified'; E = {"$($_.DateLastModified)"}}
235+
)
236+
237+
$return.logins = $logins | Select-Object $properties
238+
$return | ConvertTo-JSON -Depth 99
239+
}
240+
241+
<#
242+
.SYNOPSIS
243+
This script connects to a SQL instance running on a machine and returns
244+
information about logins.
245+
.DESCRIPTION
246+
This script will connect to SQL instances running on a machine and return
247+
information about logins configured on the instance. This script only connects
248+
to instances on a local server. It will always return data in JSON format.
249+
.PARAMETER instance_name
250+
The name of the instance running on a machine that you would like to connect to.
251+
Leave blank to get the default instance MSSQLSERVER.
252+
#>

0 commit comments

Comments
 (0)