Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to SqlServerDsc 17.0.0 and add coverage. #1414 #1415

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Versions

## [Unreleased]
* Updated Powerstig to use SqlServerDsc module version 17.0.0 and added additional coverage [#1414](https://github.com/microsoft/PowerStig/issues/1414)

## [4.24.0] - 2024-12-06

Expand Down
11 changes: 10 additions & 1 deletion Tests/Integration/DSCResources/SqlServer.config.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,27 @@ configuration SqlServer_config

[Parameter()]
[object]
$OrgSettings
$OrgSettings,

[Parameter()]
[PSCredential]
$SQLPermCredential

)

Import-DscResource -ModuleName PowerStig

Node localhost
{
$sqlPermUser = 'PlaceHolderUser'
$sqlPermPass = ConvertTo-SecureString "PlaceholderPassword" -AsPlainText -Force
$sqlPermCredential = New-Object System.Management.Automation.PSCredential ($sqlPermUser, $sqlPermPass)

$psboundParams = $PSBoundParameters
$psboundParams.SqlVersion = $psboundParams['TechnologyVersion']
$psboundParams.SqlRole = $psboundParams['TechnologyRole']
$psboundParams.ServerInstance = 'TestServer'
$psboundParams.SqlPermCredential = $sqlPermCredential
$psboundParams.Remove('TechnologyRole')
$psboundParams.Remove('ConfigurationData')
$psboundParams.Remove('TechnologyVersion')
Expand Down
69 changes: 69 additions & 0 deletions Tests/Unit/Module/SqlPermissionRule.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#region Header
. $PSScriptRoot\.tests.header.ps1
#endregion

try
{
InModuleScope -ModuleName "$($global:moduleName).Convert" {
#region Test Setup
$testRuleList = @(
@{
Name = 'NT AUTHORITY\SYSTEM'
Permission = 'CONNECTSQL,VIEWANYDATABASE'
CheckContent = 'Execute the following queries. The first query checks for Clustering and Availability Groups being provisioned in the Database Engine. The second query lists permissions granted to the Local System account.

SELECT
SERVERPROPERTY(''IsClustered'') AS [IsClustered],
SERVERPROPERTY(''IsHadrEnabled'') AS [IsHadrEnabled]

EXECUTE AS LOGIN = ''NT AUTHORITY\SYSTEM''

SELECT * FROM fn_my_permissions(NULL, ''server'')

REVERT

GO


If IsClustered returns 1, IsHadrEnabled returns 0, and any permissions have been granted to the Local System account beyond "CONNECT SQL", "VIEW SERVER STATE", and "VIEW ANY DATABASE", this is a finding.

If IsHadrEnabled returns 1 and any permissions have been granted to the Local System account beyond "CONNECT SQL", "CREATE AVAILABILITY GROUP", "ALTER ANY AVAILABILITY GROUP", "VIEW SERVER STATE", and "VIEW ANY DATABASE", this is a finding.

If both IsClustered and IsHadrEnabled return 0 and any permissions have been granted to the Local System account beyond "CONNECT SQL" and "VIEW ANY DATABASE", this is a finding.'
}
)
#endregion

foreach ($testRule in $testRuleList)
{
. $PSScriptRoot\Convert.CommonTests.ps1
}

#region Add Custom Tests Here
Describe 'Method Function Tests' {
foreach ($testRule in $testRuleList)
{

$name = Set-LoginName -CheckContent $testRule.CheckContent

Context "SqlPermission Get-Name"{
It "Should return $($name)" {
$name | Should Be $testrule.Name
}
}

$permission = Set-Permission -CheckContent $testRule.CheckContent

Context "SqlPermission Get-Permission"{
It "Should return $($permission)" {
$permission | Should Be $testrule.Permission
}
}
}
}
}
}
finally
{
. $PSScriptRoot\.tests.footer.ps1
}
48 changes: 48 additions & 0 deletions Tests/Unit/Module/SqlScriptQueryRule.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ try
Extended Events. If Extended Events are in use, and cover all the required audit events listed above, this is not a finding.'
FixText = 'This will not be used for this type of rule.'
EventId = '(14),(15),(18),(20),(102),(103),(104),(105),(106),(107),(108),(109),(110),(111),(112),(113),(115),(116),(117),(118),(128),(129),(130),(131),(132),(133),(134),(135),(152),(153),(170),(171),(172),(173),(175),(176),(177),(178)'
QueryId = '2'
Encrypt = 'Optional'
}
Permission = @{
GetScript = "SELECT who.name AS [Principal Name], who.type_desc AS [Principal Type], who.is_disabled AS [Principal Is Disabled], what.state_desc AS [Permission State], what.permission_name AS [Permission Name] FROM sys.server_permissions what INNER JOIN sys.server_principals who ON who.principal_id = what.grantee_principal_id WHERE what.permission_name = 'Alter any endpoint' AND who.name NOT LIKE '##MS%##' AND who.type_desc <> 'SERVER_ROLE' ORDER BY who.name;"
Expand Down Expand Up @@ -121,6 +123,8 @@ try
USE master
REVOKE ALTER ANY ENDPOINT TO <'account name'>
GO"
QueryId = '2'
Encrypt = 'Optional'
}
SysAdminAccount = @{
GetScript = "USE [master] SELECT name, is_disabled FROM sys.sql_logins WHERE principal_id = 1 AND is_disabled <> 1;"
Expand All @@ -137,6 +141,8 @@ try
USE master;
GO
ALTER LOGIN [sa] WITH NAME = <new name> GO"
QueryId = '2'
Encrypt = 'Optional'
}
Audit = @{
GetScript = "IF Not Exists (SELECT name AS 'Audit Name', status_desc AS 'Audit Status', audit_file_path AS 'Current Audit File' FROM sys.dm_server_audit_status WHERE status_desc = 'STARTED') Select 'Doest exist'"
Expand Down Expand Up @@ -201,6 +207,8 @@ try
SERVER_STATE_CHANGE_GROUP
TRACE_CHANGE_GROUP
See the supplemental file `"SQL 2016 Audit.sql`". "
QueryId = '1'
Encrypt = 'Optional'
}
PlainSQL = @{
GetScript = "SELECT name from sysdatabases where name like 'AdventureWorks%';"
Expand All @@ -211,6 +219,8 @@ try
If the `"AdventureWorks`" database is present, this is a finding."
FixText = "Remove the publicly available `"AdventureWorks`" database from SQL Server by running the following query:
DROP DATABASE AdventureWorks"
QueryId = '2'
Encrypt = 'Optional'
}
SaAccountRename = @{
GetScript = "SELECT name FROM sys.server_principals WHERE TYPE = 'S' and name not like '%##%'"
Expand All @@ -222,6 +232,8 @@ try
FixText = "Navigate to SQL Server Management Studio &gt;&gt; Object Explorer &gt;&gt; &lt;'SQL Server name'&gt; &gt;&gt; Security &gt;&gt; Logins &gt;&gt; click 'sa' account name.
Hit &lt;F2&gt; while the name is highlighted in order to edit the name.
Rename the 'sa' account."
QueryId = '2'
Encrypt = 'Optional'
}
TraceFileLimit = @{
GetScript = "SELECT * FROM ::fn_trace_getinfo(NULL)"
Expand All @@ -233,6 +245,8 @@ try
If auditing will outgrow the space reserved for logging before being overwritten, this is a finding."
FixText = "Configure the maximum number of audit log files that are to be generated, staying within the number of logs the system was sized to support.
Update the max_files parameter of the audits to ensure the correct number of files is defined."
QueryId = '2'
Encrypt = 'Optional'
}
ShutdownOnError = @{
GetScript = "SELECT * FROM ::fn_trace_getinfo(NULL)"
Expand Down Expand Up @@ -262,6 +276,8 @@ try
FixText = "If a trace does not exist, create a trace specification that complies with requirements.
If a trace exists, but is not set to SHUTDOWN_ON_ERROR, modify the SQL Server audit setting to immediately shutdown the database in the event of an audit failure by setting property 1 to a value of 4 or 6 for the audit.
(See the SQL Server Help page for sys.sp_trace_create for implementation details.)"
QueryId = '2'
Encrypt = 'Optional'
}
ViewAnyDatabase = @{
GetScript = "SELECT who.name AS [Principal Name], who.type_desc AS [Principal Type], who.is_disabled AS [Principal Is Disabled], what.state_desc AS [Permission State], what.permission_name AS [Permission Name] FROM sys.server_permissions what INNER JOIN sys.server_principals who ON who.principal_id = what.grantee_principal_id WHERE what.permission_name = 'View any database' AND who.type_desc = 'SERVER_ROLE' ORDER BY who.name"
Expand Down Expand Up @@ -340,6 +356,8 @@ try
"
FixText = "Remove the `"View any database`" permission access from the role that is not authorized by executing the following query:
REVOKE View any database TO &lt;'role name'&gt;"
QueryId = '2'
Encrypt = 'Optional'
}
ChangeDatabaseOwner= @{
GetScript = "select suser_sname(owner_sid) AS 'Owner' from sys.databases where name = `$(Database)"
Expand All @@ -358,6 +376,8 @@ try
Navigate to SQL Server Management Studio &gt;&gt; Object Explorer &gt;&gt; &lt;'SQL Server name'&gt; &gt;&gt; Databases &gt;&gt; right click &lt;'database name'&gt; &gt;&gt; Properties &gt;&gt; Files.
Select new database `"Owner`":
Navigate to click on […] &gt;&gt; Select new Database Owner &gt;&gt; Browse… &gt;&gt; click on box to indicate account &gt;&gt; &lt;'OK'&gt; &gt;&gt; &lt;'OK'&gt; &gt;&gt; &lt;'OK'&gt;"
QueryId = '2'
Encrypt = 'Optional'
}
AuditShutDownOnError = @{
GetScript = 'SELECT on_failure_desc FROM sys.server_audits'
Expand Down Expand Up @@ -393,6 +413,8 @@ try
GO
ALTER SERVER AUDIT [AuditNameHere] WITH (STATE = ON);
GO '
QueryId = '2'
Encrypt = 'Optional'
}
AuditFileSize = @{
GetScript = 'CREATE TABLE #AuditFileSize (Name nvarchar (30),Type_Desc nvarchar (30),Max_RollOver_Files int) INSERT INTO #AuditFileSize (Name, Type_Desc) SELECT Name, type_desc FROM sys.server_audits WHERE is_state_enabled = 1 IF (SELECT Type_Desc FROM #AuditFileSize) = ''FILE'' BEGIN UPDATE #AuditFileSize SET Max_RollOver_Files = (SELECT max_rollover_files FROM sys.server_file_audits) WHERE Name IS NOT NULL END SELECT * FROM #AuditFileSize DROP TABLE #AuditFileSize'
Expand Down Expand Up @@ -424,6 +446,8 @@ try
GO
ALTER SERVER AUDIT [AuditName] WITH (STATE = ON);
GO '
QueryId = '2'
Encrypt = 'Optional'
}
}
#endregion
Expand All @@ -435,6 +459,8 @@ try
$ruleType = Get-SqlRuleType -CheckContent ($sqlScriptQueryRule.$($rule).CheckContent)
$checkContent = Format-RuleText -RuleText $sqlScriptQueryRule.$($ruleType).CheckContent
$fixText = Format-RuleText -RuleText $sqlScriptQueryRule.$($ruleType).FixText
$queryId = Get-SqlScriptQueryId -CheckContent $sqlScriptQueryRule.$($ruleType).QueryId
$encrypt = Get-EncryptOption -CheckContent $sqlScriptQueryRule.$($ruleType).Encrypt

Context "'$ruleType' Get-SqlRuleType" {
It "Should return $($rule)" {
Expand Down Expand Up @@ -468,6 +494,28 @@ try
$result | Should be $setScript
}
}

Context 'Sql Query Id' {
It 'Should return a query id'{
$result = Get-SqlScriptQueryId -CheckContent $sqlScriptQueryRule.$($ruleType).CheckContent
if ($rule -eq 'Audit') {
$queryId = '1'
$queryId | Should be '1'
}
else {
$result | Should not be '1'
}
}
}

Context 'Encrypt Option' {
$encrypt = $sqlScriptQueryRule.$($ruleType).Encrypt

It 'Should return a encrypt option'{
$result = Get-EncryptOption -CheckContent $sqlScriptQueryRule.$($ruleType).Encrypt
$result | Should be $encrypt
}
}
}

Context 'Get-Query' {
Expand Down
38 changes: 38 additions & 0 deletions Tests/Unit/Module/SqlServerConfigurationRule.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,44 @@ try
If the value of "config_value" is "0", this is not a finding.

If the value of "config_value" is "1", review the system documentation to determine whether the use of "Replication Xps" is required and authorized. If it is not authorized, this is a finding.'
},
@{
OptionName = 'user connections'
OptionValue = '3000'
OrganizationValueRequired = $false
CheckContent = 'Review the system documentation to determine whether any concurrent session limits have been defined. If it does not, assume a limit of 10 for database administrators and 2 for all other users.

If a mechanism other than a logon trigger is used, verify its correct operation by the appropriate means. If it does not work correctly, this is a finding.

Due to excessive CPU consumption when utilizing a logon trigger, an alternative method of limiting concurrent sessions is setting the max connection limit within SQL Server to an appropriate value. This serves to block a distributed denial-of-service (DDOS) attack by limiting the attacker''s connections while allowing a database administrator to still force a SQL connection.

In SQL Server Management Studio''s Object Explorer tree:
Right-click on the Server Name >> Select Properties >> Select Connections Tab

OR

Run the query:
EXEC sys.sp_configure N''user connections''

If the max connection limit is set to 0 (unlimited) or does not match the documented value, this is a finding.

Otherwise, determine if a logon trigger exists:

In SQL Server Management Studio''s Object Explorer tree:
Expand [SQL Server Instance] >> Server Objects >> Triggers

OR

Run the query:
SELECT name FROM master.sys.server_triggers;

If no triggers are listed, this is a finding.

If triggers are listed, identify the trigger(s) limiting the number of concurrent sessions per user. If none are found, this is a finding. If they are present but disabled, this is a finding.

Examine the trigger source code for logical correctness and for compliance with the documented limit(s). If errors or variances exist, this is a finding.

Verify that the system does execute the trigger(s) each time a user session is established. If it does not operate correctly for all types of user, this is a finding.'
}
)
#endregion
Expand Down
20 changes: 20 additions & 0 deletions Tests/Unit/Module/UserRightRule.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,26 @@ try
The requirement must be documented with the ISSO.

The application account must meet requirements for application account passwords, such as length (WN16-00-000060) and required frequency of changes (WN16-00-000070).'
},
@{
DisplayName = 'Perform volume maintenance tasks'
Constant = 'SeManageVolumePrivilege'
Identity = 'NT SERVICE\MSSQLSERVER'
Force = 'false'
OrganizationValueRequired = $false
CheckContent = 'Review system configuration to determine whether IFI support has been enabled (by default in SQL Server 2016).

Start >> Control Panel >> System and Security >> Administrative Tools >> Local Security Policy >> Local Policies >> User Rights Assignment >> Perform volume maintenance tasks

The default SQL service account for a default instance is NT SERVICE\MSSQLSERVER or for a named instance is NT SERVICE\MSSQL$InstanceName.

If the SQL service account or SQL service SID has been granted "Perform volume maintenance tasks" Local Rights Assignment, this means that Instant File Initialization (IFI) is enabled.

Review the system documentation to determine if Instant File Initialization (IFI) is required.

If IFI is enabled but not documented as required, this is a finding.

If IFI is not enabled, this is not a finding.'
}
)
#endregion
Expand Down
6 changes: 6 additions & 0 deletions source/DSCResources/Resources/SqlServer.ScriptQuery.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ foreach ($instance in $ServerInstance)
TestQuery = $rule.TestScript
SetQuery = $rule.SetScript
Variable = Format-SqlScriptVariable -Database $db -Variable $($rule.Variable) -VariableValue $($rule.VariableValue)
Id = $rule.QueryId
Encrypt = $rule.Encrypt
}
}
}
Expand All @@ -51,6 +53,8 @@ foreach ($instance in $ServerInstance)
TestQuery = $rule.TestScript
SetQuery = $rule.SetScript
Variable = Format-SqlScriptVariable -Variable $($rule.Variable) -VariableValue $($rule.VariableValue)
Id = $rule.QueryId
Encrypt = $rule.Encrypt
}
continue
}
Expand All @@ -62,6 +66,8 @@ foreach ($instance in $ServerInstance)
GetQuery = $rule.GetScript
TestQuery = $rule.TestScript
SetQuery = $rule.SetScript
Id = $rule.QueryId
Encrypt = $rule.Encrypt
}
}
}
Expand Down
Loading
Loading