Friday, December 3, 2010
Running SQL Server Agent with a least privilege service account
For improved security Microsoft recommends the SQL Server Agent service account should not be a member of the local Administrators group. Being a member of the Administrator group, grants the account super-user privileges which therefore may expose you to more security vulnerabilities. By limiting access for the service accounts it will help you safeguard your system if individual services or processes are compromised.
Solution
As a best practice, SQL Server Agent service account rights should be kept as low as possible to prevent exposing your system to security risks. Making the SQL Server service account an administrator, at either a server level or a domain level, grants too many unneeded privileges and should never be done. Ideally, all the SQL Server services should run from a different account and each account should have exactly the privileges that it needs to do its job and no additional privileges.
During a new installation, SQL Server setup does not default the SQL Server engine service and SQL Server Agent service to any account. The account specification is a required step for these services. Using a local user or domain user that is not a Windows administrator is the best choice.
If the server that is running SQL Server is part of a domain and needs to access domain resources, such as file shares or uses linked server connections to other computers running SQL Server, a domain account should be used. If the server is not part of a domain, a local user that is not a Windows administrator is preferred.
The SQL Server Agent service account requires sysadmin privileges in the SQL Server instance that it is associated with. In this tip I have tried to put forth a solution by running SQL Server agent under group (SQLServer2005SQLAgentUser$ComputerName$MSSQLSERVER). This group has all the required privileges and is not part of the administrator group.
NOTE: When you install Microsoft SQL Server to run using a Microsoft Windows NT account, SQL Server sets various Windows user rights and permissions on certain files, folders, and registry keys for that account. If you later change the startup account for the SQL Server Agent service using SQL Server Configuration Manager, SQL Server automatically assigns all the required permissions and Windows user rights to the new account for you, so that you do not have to do anything else.
Steps to SQL Agent Account
STEP 1
Add the account under which you want to run the SQL Server agent service in the SQLServer2005SQLAgentUser$ComputerName$MSSQLSERVER group. (Right click my computer -> Manage -> Local Users and Groups -> Groups). Then find the group, right click on it and select Properties. This group is pre-configured with all the required permissions to run the SQL Agent service. Also, make sure the account you add to thsi group is not a member of the local administrator group. In this example I am adding "Agent test" to this group.
STEP 2
Change the log on account of the SQL Server Agent service in SQL Server Configuration Manager in SQL Server 2005. Use the account that you just added to the SQLServer2005SQLAgentUser$ComputerName$MSSQLSERVER group, in my case it is "Agent test". You may also consider doing it through services.msc, but it is recommended to do it using SQL Server Configuration Manager. The reason is, when you install SQL2005, a service master key for encryption is created. This key is then used to encrypt certificates and any other encryption keys. The service master key (SMK) is linked to the service account and changing this account can make the key invalid and then it can’t open the certificates anymore. When you change your service account through SQL Server Configuration Manager, SQL 2005 will take care of creating a SMK. By default, only members of the local administrators group can alter the service account, start, stop, pause, resume or restart a service.
STEP 3
Restart the SQL Server Agent service, so that the new account goes into affect.
STEP 4
You can check in SSMS that SQLServer2005SQLAgentUser$ComputerName$MSSQLSERVER group is a member of sysadmin role. In SSMS, go to Security -> Sever Roles. Right click on "sysadmin" and select Properties to view the scrreen below. If it is not already there, follow the steps in step 4a.
STEP 4a - add login and role
If this group is not already part of the sysadmin role, follow these steps.
Add the group SQLServer2005SQLAgentUser$ComputerName$MSSQLSERVER as a Windows authenticated login in SQL Server
And then assign the sysadmin role to this login.
Now SQL Server agent is running under an account which is not a member of the local administrators group on the server.
Note: there is limitation for using multiserver administration when the SQL Server Agent service account is not a member of the local Administrators group. Enlisting target servers to a master server may fail with the following error message: "The enlist operation failed." To resolve this error, restart both the SQL Server and the SQL Server Agent services.
Saturday, April 18, 2009
SQL Server UDF to pad a string
Problem
Unlike other relational database management systems that shall remain nameless, SQL Server's underlying coding language, T/SQL, does not have a built-in function for padding string values. I recently took it upon myself to create my own and as you'll see I got a little carried away.
Solution
The task seems simple enough: create a user-defined function that will allow padding of a string value with a finite count of a desired character. What ended up as an experiment out of necessity became a slightly more-involved function once I decided that padding position should be customizable to meet the end user's needs.
While Transact-SQL (T/SQL) does not offer a comparable function similar to LPAD or RPAD available in other RDBMSs, the SQL Server Professional does have the REPLICATE() function that can be used to build a simple user-defined function that can be used to pad a string. Let's take a look at the REPLICATE() function and what it offers before moving on to the code for the custom padding function.
REPLICATE (string_expression ,integer_expression) will allow you to replicate a character string (the string_expression parameter the number of times consecutively per the integer_expression parameter).
A simple example of this function is presented below:
SELECT REPLICATE('ABCDE|', 3)
|
Whereas the REPLICATE() function will allow you to return a string to a maximum size of 8000 bytes, the function I'll be creating will be based upon an output value of varchar(100). You will be able to modify this value to fit your needs, however I very rarely have a need to pad a string value greater than even 20 characters. I thought it worthwhile to create a single function for padding either to the left or right of the unpadded string. Then it became interesting, what if for some reason you wished to pad the center of the string? What about padding both the left and right sides of the string evenly? Whether you use those options or not, the functionality is there. The code below represents the function I've created.
CREATE FUNCTION [dbo].[usp_pad_string] |
The function expects four parameters:
- @string_unpadded - the raw string value you wish to pad.
- @pad_char - the single character to pad the raw string.
- @pad_count - the amount of times to repeat the padding character
- @pad_pattern - an integer value that determines where to insert the pad character
- 0 - all pad characters placed left of the raw string value (pad left)
- 1 - all pad characters placed right of the raw string value (pad right)
- 2 - all pad characters placed at the midpoint of the raw string value (pad center)
- 3 - the raw string value will be centered between the pad characters (pad ends)
Notes
- If either of the length of the supplied @pad_count or @string_unpadded values are odd, centering will be affected. We will show that behavior in the samples presented below.
- Since the return value of the function is limited to 100 characters, the length of parameter corresponding to the unpadded string must be sized according to allow for padding. The length of the return value and this parameter are completely customizable to fit your needs.
- While the
REPLICATE()function allows you to pad more than a single character, this function expects you'll only pad a single character.
Example 1 Evenly-Distributable Padding
--Even distribution possible |
The previous set of examples shows what results to expect when the parameters are evenly distributed. What happens though when this is not possible? This occurs under two situations: you are attempting to center-pad a raw string that has an odd number of characters or you're trying to pad an odd number of characters using the pad edges option (@pad_pattern = 3). I am not going to show you some method for splitting an odd string at a quantum level. Sorry, I am not all that brilliant as it is. Therefore I had to make a judgment call on how the behavior is going to occur using the structure of the three dimensions we have to work with here currently. The following examples will present how this function will behave.
Example 2 - Center-Pad an Odd-Length Raw String
--Pad Center |
Example 3 - Center-Edges an Odd Number of Times
--Pad Edges |
As you can see, the padding will be right-heavy in both cases. Though I doubt you'll have much use for center or edge-padding strings in Transact-SQL, you never really know.
Padding is a frequent need in returning string results to the end user of a RDBMS. This function allows you to do so as if the functionality existed inherently in T-SQL.
Saturday, March 7, 2009
SQL Server user defined function to convert MSDB integer value to time value
Problem
In a recent tip I outlined a process for converting a date, stored as an integer into a datetime data type. Date and time information for run history of SQL Server Agent jobs is stored within the msdb..sysjobshistory table as an integer data type, not as a datetime as one would expect. Most likely for at least two reasons:
- This structure is a legacy implementation from the earliest days of SQL Server
- The values are stored in separate run_date and run_time columns and until SQL Server 2008 there was not a time data type per se
As promised, this tip picks up where we left off. On converting the integer-typed run_time into a format that is more user friendly for presentation purposes.
We will still be using the same metadata repository for the SQL Server instances I administer. From the previous tip, you may remember that one of the metrics I track is based upon Job History success and failure. This information comes directly from the msdb..sysjobhistory table that resides upon each SQL Server instance.
Solution
Let's take a look again at a simple query against the msdb..sysjobshistory and msdb..sysjobs tables that hold the data we're interested in:
| sysJobHistory and sysJobs Data |
| SELECT SJ.name, SJH.run_date, SJH.run_time FROM msdb.dbo.sysjobhistory SJH INNER JOIN msdb.dbo.sysjobs SJ ON SJH.job_id = SJ.job_id WHERE SJH.step_id = 0 ORDER BY SJ.name GO |
| |
We have already covered how to convert run_date (as integer) to a datetime data type that can then be used in date calculations such as DATEADD(), DATEDIFF(), DATENAME() or DATEPART(). The purpose of this tip is to convert the run_time value (stored as an integer data type) into a format that is more presentable for end users. This can be accomplished in two manners, both outlined below. The later is a standalone user-defined function (UDF) the second takes into consideration an additional UDF outlined recently in a tip on padding string values. Reliance on this second UDF reduces the amount of code necessary. I'll be presenting the actual query execution plan for a simple query using each process so you can determine which option is the best for your environment.
As a point of reference, the msdb..sysjobhistory.run_time values are stored as an integer, in the pattern of hhmmss. Unfortunately for us, since this is an integer value, single digit values do not include a preceding zero (9:00 am for example is stored as 900, midnight as simply 0).
Option One - Standalone UDF
Let's take a look at our first option to address this problem which is a standalone UDF.
| Option One: The Standalone UDF |
| CREATE FUNCTION dbo.udf_convert_int_time_1 (@time_in INT) RETURNS VARCHAR(8) AS BEGIN DECLARE @time_out VARCHAR(8) SELECT @time_out = CASE LEN(@time_in) WHEN 6 THEN LEFT(CAST(@time_in AS VARCHAR(6)),2) + ':' + SUBSTRING(CAST(@time_in AS VARCHAR(6)), 3,2) + ':' + RIGHT(CAST(@time_in AS VARCHAR(6)), 2) WHEN 5 THEN '0' + LEFT(CAST(@time_in AS VARCHAR(6)),1) + ':' + SUBSTRING(CAST(@time_in AS VARCHAR(6)), 2,2) + ':' + RIGHT(CAST(@time_in AS VARCHAR(6)), 2) WHEN 4 THEN '00' + ':' + LEFT(CAST(@time_in AS VARCHAR(6)),2) + ':' + RIGHT(CAST(@time_in AS VARCHAR(6)), 2) ELSE '00:00:00' --midnight END --AS converted_time RETURN @time_out END GO |
The function accepts a single parameter, the integer data type time value passed to it. Depending on the length of the parameter, the output value is formatted accordingly and the result is returned. Revising the first query in this tip to include the results of this UDF applied to each record is presented below along with it's associated output and actual execution plan.
| Sample Execution |
SELECT SJ.[name], SJH.[run_date], SJH.[run_time], |
| |
| |
Option Two: Reliance on Padding of Input Variable
Recently, I published a tip on MSSQLTips.com on padding string values in Microsoft SQL Server. We will be using the UDF presented in that tip to simplify the code presented above. Afterwards we'll see what possible effect that has on performance by comparing the actual execution plan against the execution plan for the first iteration of the UDF shown above.
| Option Two: Reliance on Padding of Input Variable |
| CREATE FUNCTION dbo.udf_convert_int_time_1 (@time_in INT) RETURNS VARCHAR(8) AS BEGIN DECLARE @time_out VARCHAR(8) SELECT @time_out = CASE LEN(@time_in) WHEN 6 THEN LEFT(CAST(@time_in AS VARCHAR(6)),2) + ':' + SUBSTRING(CAST(@time_in AS VARCHAR(6)), 3,2) + ':' + RIGHT(CAST(@time_in AS VARCHAR(6)), 2) WHEN 5 THEN '0' + LEFT(CAST(@time_in AS VARCHAR(6)),1) + ':' + SUBSTRING(CAST(@time_in AS VARCHAR(6)), 2,2) + ':' + RIGHT(CAST(@time_in AS VARCHAR(6)), 2) WHEN 4 THEN '00' + ':' + LEFT(CAST(@time_in AS VARCHAR(6)),2) + ':' + RIGHT(CAST(@time_in AS VARCHAR(6)), 2) ELSE '00:00:00' --midnight END --AS converted_time RETURN @time_out END GO |
By utilizing the usp_pad_string() function we can eliminate the CASE code structure from the dbo.udf_convert_int_time_2 UDF. The padding function expects four parameters: the string value to pad, the padding character, the number of instances to apply the pad, and the padding placement. Please review the full structure of the usp_pad_string UDF in the original article. It will pad the integer value so that further processing can be consistently applied without concern for length. Running a comparable query to the one previously presented returns the following results and execution plan.
| Sample Execution |
SELECT SJ.[name], SJH.[run_date], SJH.[run_time], |
| |
| |
The additional function call to the padding UDF has no apparent additional overhead on the execution of the query. Which option you choose would be up to you, dependent upon your preference for UDF reliance and embedding of UDFs. Ultimately this process is based upon conversion of time-of-day data into a presentable format. While the CONVERT() function is capable of converting string values to presentable formats when passed datetime values, there is no functionality for time-only values.