There are many different ways to secure your database. In this blog post we will give most of them a quick description. This blog is the third one of the ongoing SQL Security blog posts. In the previous blog post, the different ways to tackle security on the SQL Instance side have been discussed. Now it is time to look into what tools and technologies the engine provides to make a database itself more secure. This will not be a deep-dive, but is intended to give you an overview of the different ways to secure your database. I will place relevant links throughout the post in case you want a more detailed explanation/deep-dive of a certain topic. So consider this blog post as some kind of a repository which you can use as an easy overview and too quickly navigate towards more detailed explanations of these topics. I hope this will save you some bookmarks.
Users:
Logins and users are sometimes used interchangeably, but within SQL Server, there is a clear distinction. A login is used to authenticate on the server side and a user is used for access on the database side. In each database a user can be associated with one server login. Logins might not be associated with users in all databases, and it is possible for a user to exist without any association to a login.
Something to take into account is that sometimes database users get orphaned. This happens when a database user is based on a login, but this login no longer exists. Here is a guide on how you can troubleshoot this. You can give the user either the same or a different name compared to the login used for association. There are 13 types of users to create. How you will utilize these all depends on what u actually want to do.
Schemas:
Every database starts with its defaults schemas. Dbo , guest, sys, INFORMATION_SCHEMA. In a lot of cases dbo is used, but we can create new schemas. With this, we have a way to make a separate container for database objects. This has a couple of advantages like being able to assign narrower rights/access on the database, more easily transfer objects between two different schemas, reuse of same table names because of the prefix that you need to include while using a different schema (example: intern.users and extern.users). For more details, I found this post extremely useful.
Database roles:
As with schemas, when you create a database there will be several default database-level roles. As before, we can create our own and do a lot with those. We can grant/deny/revoke very specific access through these roles. Like, if we created a new schema and we have a user that only needs read access on all objects of this schema we can create a new db_role as follows:
This is just the tip of the iceberg. Here you can find more about granting schema permissions but it does not stop there. You can also grant rights on specific objects. For example, a role that needs read-write access to all user tables regardless of schema. Or if we want the intern_reader role to be able to read from all tables but one we can do DENY SELECT ON intern.paycheck for example. Thus in short with database roles we can manage who has what access on the database and which objects/schemas.
Ownership Chaining:
When you create a new schema, it will get an owner assigned. Unless specified otherwise this will be the dbo user. Each new object created under this schema will have the same owner unless specified otherwise. To specify an owner as follows;
ALTER AUTHORIZATION ON SCHEMA/OBJECT :: SCHEMANAME/OBJECTNAME to OWNER
This can be used as one more way to narrow down who can see with in your database. With this technique you can give a user permission to select from the view but not directly from the connected tables, or execute stored procedures while giving rights the underlying tables if the objects are owned by the same owner.
Here you can find a blogpost that can give you a more in depth understanding of this.
This feature is configured on table level and is used to determine what users can view which rows. Because it is configured in the database, there is less need to manage security on the application layer.
RLS works for example like this. Imagine there is a company with a CEO, several team managers and the employees divided over several teams. You want to have everybody be able to select on the salary table, but not everybody should be able to see everybody’s salaries. The employees should be able to only view their own salary, the team leads their own salary plus their team members and the CEO to be able to view all. With RLS you can achieve this. You will need to create user or specific database_roles for access management. When using roles, IS_MEMBER() shall need to be used int the function. An inline table-valued function, used for the filtering predicate for the table. And the security policy on the table in which we reference the inline table-valued function created previously.
Here you can find a great blog with examples about this technology. If however you are still working on an older version of SQL, this way can also be used to implement RLS.
DDM and RLS may seem similar at first view. Both features are intended to make the viewing of sensitive data by unauthorised users impossible and both are on the database level. But how it works is very different. With DDM you will be able to select all the data, but the values of this data will be masked while with RLS you will only see the relevant data.
You configure this data masking in the column definition of the table. After your datatype definition you will have to add “MASKED WITH (FUNCTION = “ and then your NULL, not null or other definitions. Be warned, there are some limitations and also a security flaw. The flaw works like this: while the function does mask the data for the user, it still holds its true values. So if a user with access to the salary table, does a select * from salary where salary > their salary then they can still know who is earning more than them. If that user wants they can be more specific in that ‘where’ clause to get more specific details, all while the query results show them nothing.
Since SQL Server 2022 there is also a new feature added to this called DDM with granular permissions, this is also relevant for Azure DBs.
A second form called Static Data Masking was promised to be implemented as preview in SSMS 18, but Microsoft did not deliver. It should enable you to use a feature which will randomise data of your choice. This change would be permanent and would be great for example to alter data from Production you use to refresh in DEV. This should have been implemented into SSMS 18.5, but it was not even in SSMS 19. So let’s move on
This feature will be encrypting your data when it is at rest. This means your page file when it gets written to disk will be encrypted and when it gets read into memory it will be decrypted. TDE will also encrypt your backups. This means if the scenario plays out that your physical files of the DB or the backup gets stolen, the thief will not be able to simply open it without the needed keys and passwords. Once you have done the setup for one of your user DBs your Tempdb will also be encrypted in the same way. Other system DBs cannot be encrypted this way. This feature does have a little impact on performance so be aware. It does not have impact on the database size.
While TDE protects the data at rest on the server side, it doesn’t protect data in transit or from unauthorized access within the company. For this, Microsoft introduced Always Encrypted (AE) in SQL Server 2016. AE does both, it allows clients to encrypt sensitive data inside client applications and never reveal the encryption keys to the Database Engine. It allows you to encrypt only certain columns, rather than the entire database. This way sensitive data only gets to be viewed by those who use the data. Those who manage the data like us DBAs will not be able to view the data.
I found this and this to be very helpful for an in depth explanation.
In conclusion, there are many versatile methods to secure data both inside and outside the database. However, it’s crucial to adhere to the Principle of Least Privilege, ensuring that users have only the necessary permissions.
The next blog in this series will be about SQL Ledgers. Until soon!
Cookie | Duration | Description |
---|---|---|
ARRAffinity | session | ARRAffinity cookie is set by Azure app service, and allows the service to choose the right instance established by a user to deliver subsequent requests made by that user. |
ARRAffinitySameSite | session | This cookie is set by Windows Azure cloud, and is used for load balancing to make sure the visitor page requests are routed to the same server in any browsing session. |
cookielawinfo-checkbox-advertisement | 1 year | Set by the GDPR Cookie Consent plugin, this cookie records the user consent for the cookies in the "Advertisement" category. |
cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
CookieLawInfoConsent | 1 year | CookieYes sets this cookie to record the default button state of the corresponding category and the status of CCPA. It works only in coordination with the primary cookie. |
elementor | never | The website's WordPress theme uses this cookie. It allows the website owner to implement or change the website's content in real-time. |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |
Cookie | Duration | Description |
---|---|---|
__cf_bm | 30 minutes | Cloudflare set the cookie to support Cloudflare Bot Management. |
pll_language | 1 year | Polylang sets this cookie to remember the language the user selects when returning to the website and get the language information when unavailable in another way. |
Cookie | Duration | Description |
---|---|---|
_ga | 1 year 1 month 4 days | Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors. |
_ga_* | 1 year 1 month 4 days | Google Analytics sets this cookie to store and count page views. |
_gat_gtag_UA_* | 1 minute | Google Analytics sets this cookie to store a unique user ID. |
_gid | 1 day | Google Analytics sets this cookie to store information on how visitors use a website while also creating an analytics report of the website's performance. Some of the collected data includes the number of visitors, their source, and the pages they visit anonymously. |
ai_session | 30 minutes | This is a unique anonymous session identifier cookie set by Microsoft Application Insights software to gather statistical usage and telemetry data for apps built on the Azure cloud platform. |
CONSENT | 2 years | YouTube sets this cookie via embedded YouTube videos and registers anonymous statistical data. |
vuid | 1 year 1 month 4 days | Vimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. |
Cookie | Duration | Description |
---|---|---|
ai_user | 1 year | Microsoft Azure sets this cookie as a unique user identifier cookie, enabling counting of the number of users accessing the application over time. |
VISITOR_INFO1_LIVE | 5 months 27 days | YouTube sets this cookie to measure bandwidth, determining whether the user gets the new or old player interface. |
YSC | session | Youtube sets this cookie to track the views of embedded videos on Youtube pages. |
yt-remote-connected-devices | never | YouTube sets this cookie to store the user's video preferences using embedded YouTube videos. |
yt-remote-device-id | never | YouTube sets this cookie to store the user's video preferences using embedded YouTube videos. |
yt.innertube::nextId | never | YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen. |
yt.innertube::requests | never | YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen. |
Cookie | Duration | Description |
---|---|---|
WFESessionId | session | No description available. |