{"id":83448,"date":"2019-02-25T15:59:49","date_gmt":"2019-02-25T15:59:49","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=83448"},"modified":"2022-04-24T21:15:30","modified_gmt":"2022-04-24T21:15:30","slug":"introduction-to-sql-server-security-part-3","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/devops\/data-privacy-and-protection\/introduction-to-sql-server-security-part-3\/","title":{"rendered":"Introduction to SQL Server Security \u2014 Part 3"},"content":{"rendered":"<h4>The series so far:<\/h4>\n<ol>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/data-protection-and-privacy\/introduction-to-sql-server-security-part-1\/\">Introduction to SQL Server Security \u2014 Part 1<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/data-protection-and-privacy\/introduction-to-sql-server-security-part-2\/\">Introduction to SQL Server Security \u2014 Part 2<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/data-protection-and-privacy\/introduction-to-sql-server-security-part-3\/\">Introduction to SQL Server Security \u2014 Part 3<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/data-protection-and-privacy\/introduction-to-sql-server-security-part-4\">Introduction to SQL Server Security \u2014 Part 4<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/data-protection-and-privacy\/introduction-to-sql-server-security-part-5\">Introduction to SQL Server Security \u2014 Part 5<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/data-protection-and-privacy\/introduction-to-sql-server-security-part-6\/\">Introduction to SQL Server Security\u00a0<span>\u2014 <\/span>Part 6<\/a><\/li>\n<li style=\"list-style-type: none;\">\u00a0<\/li>\n<\/ol>\n\n<p>Microsoft introduced contained databases in SQL Server 2012. A contained database is one that is isolated from other databases and from the SQL Server instance that hosts the database. The database maintains much of its own metadata and supports database-level authentication, eliminating the need for server-based logins. As a result, a contained database is more portable than a traditional, non-contained database. It can also simplify database development and administration, as well as make it easier to support Always On Availability Groups.<\/p>\n<p>Controlling access to a contained database is similar to a non-contained database, except for a few important differences. In the first two articles in this series, I touched briefly upon the topic of contained databases when discussing SQL Server access control. In this article, I dig deeper into contained databases and offer several examples that show how to create contained database users, duplicate users across multiple contained databases, and unlink database users from their server-level logins.<\/p>\n<h2>Setting Up Your Environments<\/h2>\n<p>To try out the examples in this article, you need a test environment that includes a contained database. On my system, I used SQL Server Management Studio (SSMS) to create a simple database and populate it with data from the <code>WideWorldImporters<\/code> database, although you can use any data that fits your needs.<\/p>\n<p>Before you can implement a contained database, you must enable the SQL Server instance to support this feature, if it\u2019s not already enabled. To use T-SQL to enable contained databases, run the following <code>EXECUTE<\/code> statement:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXEC sp_configure 'contained database authentication', 1;\r\nGO\r\nRECONFIGURE;\r\nGO<\/pre>\n<p>The <code>EXECUTE<\/code> statement calls the <code>sp_configure<\/code> stored procedure to set the <code>contained<\/code> <code>database<\/code> <code>authentication<\/code> setting to <code>1<\/code> (on). You should then run the <code>RECONFIGURE<\/code> statement to implement the changes.<\/p>\n<p>For the examples in this article, create the <code>ImportSales1<\/code> contained database, using the following T-SQL script:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">USE master;\r\nGO\r\nDROP DATABASE IF EXISTS ImportSales1;\r\nGO\r\nCREATE DATABASE ImportSales1\r\nCONTAINMENT = PARTIAL;\r\nGO<\/pre>\n<p>When you create a database, you can specify that it should be contained by including the <code>CONTAINMENT<\/code> clause in the <code>CREATE<\/code> <code>DATABASE<\/code> statement and set its value to <code>PARTIAL<\/code>. The default value is <code>NONE<\/code>, which disables the contained database feature. The <code>PARTIAL<\/code> value is used because SQL Server supports only partially contained databases, as opposed to fully contained databases. Currently, SQL Server does not support fully contained databases.<\/p>\n<p>A partially contained database allows you to implement uncontained features that cross the database boundary. For example, you can create a database user that is linked to a SQL Server login in a partially contained database. Fully contained databases do not allow the use of uncontained features.<\/p>\n<p>After you create the <code>ImportSales1<\/code> database, you can add tables and then populate them, just like you can with a non-contained database. To support the examples in the rest of the article, use the following T-SQL script:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">USE ImportSales1;\r\nGO\r\nCREATE SCHEMA Sales;\r\nGO\r\nCREATE TABLE Sales.Customers(\r\n  CustID INT NOT NULL PRIMARY KEY,\r\n  Customer NVARCHAR(100) NOT NULL,\r\n  Contact NVARCHAR(50) NOT NULL,\r\n  Category NVARCHAR(50) NOT NULL);\r\nGO\r\nINSERT INTO Sales.Customers(CustID, Customer, Contact, Category) \r\nSELECT CustomerID, CustomerName, \r\n  PrimaryContact, CustomerCategoryName\r\nFROM WideWorldImporters.Website.Customers\r\nWHERE BuyingGroupName IS NOT NULL;\r\nGO<\/pre>\n<p>The script creates the <code>Sales<\/code> schema, adds the <code>Customers<\/code> table to the schema, and then populates the table with data from the <code>WideWorldImporters<\/code> database. The <code>SELECT<\/code> statement\u2019s <code>WHERE<\/code> clause limits the results to those rows with a <code>BuyingGroupName<\/code> value that is <code>NOT<\/code> <code>NULL<\/code> (402 rows on my system). If you create a different structure or use different data, be sure to modify the remaining examples as necessary.<\/p>\n<h2>Creating Database Users<\/h2>\n<p>In SQL Server, you can create users that are specific to a contained database and not linked to server-level logins. Contained users make it possible to maintain the separation between the contained database and the SQL Server instance, so it\u2019s easier to move the database between instances.<\/p>\n<p>SQL Server supports two types of contained users: <em>SQL user with password<\/em> and <em>Windows user<\/em>. The password-based user is a database user that is assigned a password for authenticating directly to the database. The user is not associated with any Windows accounts.<\/p>\n<p>To create a password-based user, you must include a <code>WITH<\/code> <code>PASSWORD<\/code> clause in your <code>CREATE<\/code> <code>USER<\/code> statement. For example, the following <code>CREATE<\/code> <code>USER<\/code> statement defines a user named <code>sqluser02<\/code> and assigns the password <code>tempPW@56789<\/code> to the user:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">USE ImportSales1;\r\nGO\r\nCREATE USER sqluser02\r\nWITH PASSWORD = 'tempPW@56789';\r\nGO<\/pre>\n<p>When a password-based user tries to access a contained database, the user account is authenticated at the database level, rather than the server level. In addition, all authorization granted through assigned permissions is limited to the database.<\/p>\n<p>The second type of contained database user\u2014<em>Windows user<\/em>\u2014is based on a Windows account, either local or domain. The Windows computer or Active Directory service authenticates the user and passes an access token onto the database. As with password-based users, authorization also occurs within the database according to how permissions have been granted or denied.<\/p>\n<p>When you create a Windows user, be sure that the Windows account is not already associated with a login. If you try to create a Windows user with the same name as a login, SQL Server will automatically associate that user with the login, which means that the user will not be contained.<\/p>\n<p>In the following example, the <code>CREATE<\/code> <code>USER<\/code> statement defines a user based on the <code>winuser02<\/code> local account, which I created on the <code>win10b<\/code> computer:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">CREATE USER [win10b\\winuser02];\r\nGO<\/pre>\n<p>Whenever referencing a Windows account in this way, you must use the following format, including the brackets (unless enclosing the account in quotes):<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">[&lt;domain_or_computer&gt;\\&lt;windows_account&gt;]<\/pre>\n<p>After you\u2019ve created your contained users, you can grant, deny, or revoke permissions just like you can with any database users. For example, the following <code>GRANT<\/code> statement grants the <code>SELECT<\/code> permission on the <code>Sales<\/code> schema to both users:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">GRANT SELECT ON SCHEMA::Sales TO sqluser02, [win10b\\winuser02];\r\nGO<\/pre>\n<p>You can also add contained users to fixed and user-defined database roles, and assign permissions to the user-defined roles. For more information about creating database users and granting them permissions, refer back to the second article in this series.<\/p>\n<h2>Creating Duplicate Database Users<\/h2>\n<p>When working with contained databases, you might find that some users need to be able to access multiple databases. For password-based users (<em>SQL user with password<\/em>), you should create the same user in each database, assigning the same password and security identifier (SID) to each user instance.<\/p>\n<p>One way to get the SID is to retrieve it from the <code>sys.database_principals<\/code> system view after creating the first user, as shown in the following example:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">USE ImportSales1;\r\nGO\r\nSELECT SID FROM sys.database_principals WHERE name = 'sqluser02';<\/pre>\n<p>The <code>SELECT<\/code> statement returns the <code>SID<\/code> value for the <code>sqluser02<\/code> user in the <code>ImportSales1<\/code> database. The returned value will be unique to that user and will be in a form similar to the following:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">0x0105000000000009030000008F5AC110DFB07044AFDADA6962B63B03<\/pre>\n<p>You should use this value whenever you duplicate the user in other contained databases. To see how this works, you can create a database similar to the <code>ImportSales1<\/code> database but instead name it <code>ImportSales2<\/code>, as in the following example:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">USE master;\r\nGO\r\nDROP DATABASE IF EXISTS ImportSales2;\r\nGO\r\nCREATE DATABASE ImportSales2\r\nCONTAINMENT = PARTIAL;\r\nGO\r\nUSE ImportSales2;\r\nGO\r\nCREATE SCHEMA Sales;\r\nGO\r\nCREATE TABLE Sales.Customers(\r\n  CustID INT NOT NULL PRIMARY KEY,\r\n  Customer NVARCHAR(100) NOT NULL,\r\n  Contact NVARCHAR(50) NOT NULL,\r\n  Category NVARCHAR(50) NOT NULL);\r\nGO\r\nINSERT INTO Sales.Customers(CustID, Customer, Contact, Category) \r\nSELECT CustomerID, CustomerName, \r\n  PrimaryContact, CustomerCategoryName\r\nFROM WideWorldImporters.Website.Customers\r\nWHERE BuyingGroupName IS NULL;\r\nGO<\/pre>\n<p>The script creates the <code>ImportSales2<\/code> database, adds the <code>Sales<\/code> schema to the database, adds the <code>Customers<\/code> table to the schema, and populates the table with 261 rows of data from the <code>WideWorldImporters<\/code> database. In this case, the <code>WHERE<\/code> clause filters for <code>NULL<\/code> values, rather than <code>NOT<\/code> <code>NULL<\/code>.<\/p>\n<p>Next, create the <code>sqluser02<\/code> user in the <code>ImportSales2<\/code> database, only this time, include an <code>SID<\/code> clause that specifies the user\u2019s SID from the <code>ImportSales1<\/code> database, as shown in the following example:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">USE ImportSales2;\r\nGO\r\nCREATE USER sqluser02\r\nWITH PASSWORD = 'tempPW@56789',\r\nSID = 0x0105000000000009030000008F5AC110DFB07044AFDADA6962B63B03;\r\nGO<\/pre>\n<p>To create a duplicate Windows-based user, use the same <code>CREATE<\/code> <code>USER<\/code> statement you used in the <code>ImportSales1<\/code> database:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">CREATE USER [win10b\\winuser02];\r\nGO<\/pre>\n<p>You can also use the same <code>GRANT<\/code> statement to assign the <code>SELECT<\/code> permission to the <code>Sales<\/code> schema for both users:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">GRANT SELECT ON SCHEMA::Sales TO sqluser02, [win10b\\winuser02];\r\nGO<\/pre>\n<p>That\u2019s all there is to creating duplicate password-based and Windows-based users. You can use the same format for creating duplicate users in additional contained databases, depending on your data access requirements.<\/p>\n<h2>Running T-SQL Queries<\/h2>\n<p>To test the users you created in the contained databases, you can use an <code>EXECUTE<\/code> <code>AS<\/code> statement in SSMS to run queries within the execution context of a specific contained user. For example, the following T-SQL sets the execution context to the <code>sqluser02<\/code> user, runs a <code>SELECT<\/code> statement against the <code>Customers<\/code> table, and then uses the <code>REVERT<\/code> statement to return to the original execution context:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXECUTE AS USER = 'sqluser02'; \r\nSELECT * FROM Sales.Customers\r\nREVERT;\r\nGO <\/pre>\n<p>On my system, the <code>SELECT<\/code> statement returns 261 rows because the statement ran within the context of the last specified database, <code>ImportSales2<\/code>. However, the <code>sqluser02<\/code> user exists in both databases, sharing the same name, password, and SID, so you should be able to query the <code>Customers<\/code> table in both databases, as in the following example:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXECUTE AS USER = 'sqluser02'; \r\nSELECT * FROM ImportSales1.Sales.Customers\r\nUNION ALL\r\nSELECT * FROM ImportSales2.Sales.Customers;\r\nREVERT;  \r\nGO <\/pre>\n<p>Unfortunately, if you try to run the statement, you\u2019ll receive an error similar to the following:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk \">The server principal \"S-1-9-3-281107087-1148235999-1775950511-54244962\" \r\nis not able to access the database \"ImportSales1\" under \r\nthe current security context.<\/pre>\n<p>The problem is not with how you\u2019ve set up the user accounts or query, but rather with how the <code>TRUSTWORTHY<\/code> database property is configured. The property determines whether the SQL Server instance trusts the database and the contents within it. Although this might seem to have little to do with contained databases, the <code>TRUSTWORTHY<\/code> property must be set to <code>ON<\/code> for the <code>ImportSales2<\/code> database because you\u2019re running the query within the context of that database but trying to access data in the <code>ImportSales1<\/code> database.<\/p>\n<p>By default, the <code>TRUSTWORTHY<\/code> property is set to <code>OFF<\/code> to reduce certain types of threats. You can find more information about the property in the SQL Server help topic <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/security\/trustworthy-database-property?view=sql-server-2017\">TRUSTWORTHY Database Property<\/a>.<\/p>\n<p>Before setting the property, you must be sure you\u2019re working in the correct execution context. If you\u2019ve been following along with the examples, your session might still be operating within the context of the <code>sqluser02<\/code> user. This is because the <code>UNION<\/code> <code>ALL<\/code> query in the last example failed, which means that the <code>REVERT<\/code> statement never ran. As a result, your current SQL Server session is still be running within the execution context of the <code>sqluser02<\/code> user. To correct this situation, simply rerun the <code>REVERT<\/code> statement.<\/p>\n<p>At any point, you can verify the current execution context by calling the <code>CURRENT_USER<\/code> function:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">SELECT CURRENT_USER;<\/pre>\n<p>Once you\u2019ve established that you\u2019re working within the context of the correct user, run the following <code>ALTER<\/code> <code>DATABASE<\/code> statement to set the <code>TRUSTWORTHY<\/code> property to <code>ON<\/code>:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">ALTER DATABASE ImportSales2 SET TRUSTWORTHY ON;\r\nGO<\/pre>\n<p>Now when you run the following query, it should return the 663 rows from the two tables:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXECUTE AS USER = 'sqluser02'; \r\nSELECT * FROM ImportSales1.Sales.Customers\r\nUNION ALL\r\nSELECT * FROM ImportSales2.Sales.Customers;\r\nREVERT;\r\nGO <\/pre>\n<p>You should also receive the same results if you run the query under the execution context of the <code>win10b\\winuser02<\/code> user, as shown in the following example:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXECUTE AS USER = 'win10b\\winuser02'; \r\nSELECT * FROM ImportSales1.Sales.Customers\r\nUNION ALL\r\nSELECT * FROM ImportSales2.Sales.Customers;\r\nREVERT;\r\nGO<\/pre>\n<p>I created and ran all the above examples in SSMS. If you try them out for yourselves, you\u2019ll also likely use SSMS or SQL Server Data Tools (SSDT). In the real world, however, most connections will be through third-party scripts, utilities, or applications. In such cases, the connection string that establishes the connection to the contained database must specify that database as the initial catalog. Otherwise the connection will fail.<\/p>\n<h2>Unlinking Database Users from Their Server Logins<\/h2>\n<p>Because SQL Server contained databases are only partially contained, they can include users mapped to server logins. The users might have existed before changing the database to a contained state, or they might have been added after the fact. In either case, the database is less portable because of its login connections.<\/p>\n<p>SQL Server provides the <code>sp_migrate_user_to_contained<\/code> system stored procedure for quickly unlinking database users from their associated SQL Server logins. To see how this works, start by creating the following user in the <code>ImportSales1<\/code> database:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">USE ImportSales1;\r\nGO\r\nCREATE USER sqluser03 FOR LOGIN sqluser01;\r\nGRANT SELECT ON SCHEMA::Sales TO sqluser03;\r\nGO<\/pre>\n<p>The script creates the <code>sqluser03<\/code> user based on the <code>sqluser01<\/code> login and grants to the user the <code>SELECT<\/code> permission on the <code>Sales<\/code> schema. (If the <code>sqluser01<\/code> login doesn\u2019t exist on your system, you can also use a different login or refer to the second article in this series for information about creating the <code>sqluser01<\/code> login.)<\/p>\n<p>After you create the database user, you can test that it has the expected access by running the following query:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXECUTE AS USER = 'sqluser03'; \r\nSELECT * FROM ImportSales1.Sales.Customers;\r\nREVERT;\r\nGO <\/pre>\n<p>The query should return all the rows from the <code>Customers<\/code> table in the <code>ImportSales1<\/code> database.<\/p>\n<p>If you view the user\u2019s properties in Object Explorer in SSMS, you\u2019ll find that the <code>General<\/code> tab shows the associated login as <code>sqluser01<\/code> and the user type as <code>SQL<\/code> <code>user<\/code> <code>with<\/code> <code>login<\/code>, as shown in Figure 1<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"709\" height=\"356\" class=\"wp-image-83449\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/02\/word-image-69.png\" \/><\/p>\n<p class=\"caption\">Figure 1. Database user based on a SQL Server login<\/p>\n<p>To unlink this user from the SQL Server login, run the <code>sp_migrate_user_to_contained<\/code> stored procedure, specifying the database user that you want to migrate, as shown in the following example:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXEC sp_migrate_user_to_contained   \r\n@username = N'sqluser03',  \r\n@rename = N'keep_name',  \r\n@disablelogin = N'do_not_disable_login';<\/pre>\n<p>The <code>sp_migrate_user_to_contained<\/code> system stored procedure takes the following three parameters:<\/p>\n<ul>\n<li>The <code>@username<\/code> parameter is the database user.<\/li>\n<li>The <code>@rename<\/code> parameter determines whether to use the database user or the server login for the name. The <code>keep_name<\/code> value retains the database user name. The <code>copy_login_name<\/code> uses the login name.<\/li>\n<li>The <code>@disablelogin<\/code> parameter determines whether to disable the login. In this case, the login will not be disabled. To disable the login, instead, specify the <code>disable_login<\/code> value.<\/li>\n<\/ul>\n<p>After you run the <code>EXECUTE<\/code> statement, reopen the properties for the <code>sqluser03<\/code> user. You\u2019ll find that a login is no longer associated with the user and that the user type has been changed to <code>SQL<\/code> <code>user<\/code> <code>with<\/code> <code>password<\/code>, as shown in Figure 2.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"735\" height=\"457\" class=\"wp-image-83450\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2019\/02\/word-image-70.png\" \/><\/p>\n<p class=\"caption\">Figure 2. Password-based contained database user<\/p>\n<p>When you unlink a database user from a login, SQL Server assign\u2019s the login\u2019s password to the user, as indicated in the figure. As a security best practice, you should reset the user\u2019s password at this point. If you were to now rerun the following query, you should again receive the same rows from the <code>ImportSales1<\/code> database:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXECUTE AS USER = 'sqluser03'; \r\nSELECT * FROM ImportSales1.Sales.Customers;\r\nREVERT;\r\nGO <\/pre>\n<p>By unlinking the login from the database user, you can take full advantage of the portability inherent in contained databases. Be aware, however, that the <code>sp_migrate_user_to_contained<\/code> stored procedure works only for SQL Server logins and not Windows logins.<\/p>\n<h2>Securing SQL Server Contained Databases<\/h2>\n<p>Contained databases can make it easier to move a database from one SQL Server instance to another, without having to worry about duplicating login information between those instances. However, before implementing contained databases, you should be familiar with Microsoft\u2019s security guidelines, described in the SQL Server help topic <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/databases\/security-best-practices-with-contained-databases?view=sql-server-2017\">Security Best Practices with Contained Databases<\/a>. The topic explains some of the subtler aspects of controlling access to contained databases, particularly when it comes to roles and permissions.<\/p>\n<p>Aside from these guidelines, you\u2019ll find that controlling access to a contained database works much like a non-contained database. You might need to duplicate users across multiple contained databases or unlink database users from their server logins, but these are relatively straightforward processes, much like controlling access in general. Once you understand the basics, you should have little trouble supporting more complex scenarios.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>SQL Server supports partially contained databases. This gives you the option of adding database users with a password inside the database. This makes the database easier to move to another instance or participate in an Always On Availability Group. In this article, Robert Sheldon explains how to work with users in contained databases.&hellip;<\/p>\n","protected":false},"author":221841,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143514,143527],"tags":[68855],"coauthors":[6779],"class_list":["post-83448","post","type-post","status-publish","format-standard","hentry","category-data-privacy-and-protection","category-database-administration-sql-server","tag-sql-provision"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/83448","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221841"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=83448"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/83448\/revisions"}],"predecessor-version":[{"id":83456,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/83448\/revisions\/83456"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=83448"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=83448"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=83448"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=83448"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}